From fcfee4ec7ab3f1ebfe4611aaa44a5982a309f399 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Wed, 23 Jul 2025 18:46:11 -0700 Subject: [PATCH] [mle] introduce `PrevRoleRestorer` to manage role restoration (#11731) This commit introduces `Mle::PrevRoleRestorer` to consolidate the logic for restoring a device's previous role (child or router/leader) after an MLE restart. This new class replaces and encapsulates the functionality from the now-removed `RouterRoleRestorer` class and `RestorePrevRole()` method. `PrevRoleRestorer` manages its own timer and retransmission logic. It handles sending Child Update Requests to restore a child role, or multicast Link Requests to restore a router/leader role. It also adds a small random delay before the first transmission attempt to avoid synchronized transmissions when multiple devices restart at once. This change simplifies the `Mle` class by centralizing all role restoration logic into a single component, making future enhancements to this process easier. --- src/core/thread/mle.cpp | 210 ++++++++++++++++++++++++++---------- src/core/thread/mle.hpp | 118 +++++++++++++------- src/core/thread/mle_ftd.cpp | 88 ++------------- 3 files changed, 242 insertions(+), 174 deletions(-) diff --git a/src/core/thread/mle.cpp b/src/core/thread/mle.cpp index 16d4c1669..f057d18f8 100644 --- a/src/core/thread/mle.cpp +++ b/src/core/thread/mle.cpp @@ -74,6 +74,7 @@ Mle::Mle(Instance &aInstance) , mNeighborTable(aInstance) , mDelayedSender(aInstance) , mSocket(aInstance, *this) + , mPrevRoleRestorer(aInstance) , mDetacher(aInstance) , mRetxTracker(aInstance) , mAnnounceHandler(aInstance) @@ -114,7 +115,6 @@ Mle::Mle(Instance &aInstance) , mAdvertiseTrickleTimer(aInstance, Mle::HandleAdvertiseTrickleTimer) , mChildTable(aInstance) , mRouterTable(aInstance) - , mRouterRoleRestorer(aInstance) #endif // OPENTHREAD_FTD { mParent.Init(aInstance); @@ -231,7 +231,7 @@ Error Mle::Start(StartMode aMode) mReattachState = (Get().Restore() == kErrorNone) ? kReattachActive : kReattachStop; - if (RestorePrevRole() == kErrorNone) + if (mPrevRoleRestorer.Start() == kErrorNone) { ExitNow(); } @@ -258,6 +258,7 @@ void Mle::Stop(StopMode aMode) VerifyOrExit(!IsDisabled()); mDelayedSender.Stop(); + mPrevRoleRestorer.Stop(); mAnnounceHandler.Stop(); Get().Stop(); SetStateDetached(); @@ -266,54 +267,12 @@ void Mle::Stop(StopMode aMode) Get().RemoveUnicastAddress(mMeshLocalRloc); Get().RemoveUnicastAddress(mMeshLocalEid); -#if OPENTHREAD_FTD - mRouterRoleRestorer.Stop(); -#endif - SetRole(kRoleDisabled); exit: mDetacher.HandleStop(); } -Error Mle::RestorePrevRole(void) -{ - // Restores the device to its previously saved role after MLE - // start. - // - // Returns `kErrorNone` if the restoration process begins - // successfully. An error is returned if: - // - No previous role was saved. - // - The saved role info is inconsistent (e.g., RLOC16 indicates a - // router/leader, but `mLastSavedRole` doesn't match). - // - The last role is "Child," but no valid parent is available. - - Error error = kErrorFailed; - - VerifyOrExit(IsDetached()); - VerifyOrExit(GetRloc16() != kInvalidRloc16); - - if (IsRouterRloc16(GetRloc16())) - { -#if OPENTHREAD_FTD - VerifyOrExit(mLastSavedRole == kRoleRouter || mLastSavedRole == kRoleLeader); - VerifyOrExit(IsRouterEligible()); - Get().SetRxOnWhenIdle(true); - mRouterRoleRestorer.Start(mLastSavedRole); - error = kErrorNone; -#endif - ExitNow(); - } - - VerifyOrExit(mLastSavedRole == kRoleChild); - VerifyOrExit(mParent.IsStateValidOrRestoring()); - IgnoreError(SendChildUpdateRequestToParent()); - error = kErrorNone; - -exit: - return error; -} - const Counters &Mle::GetCounters(void) { UpdateRoleTimeCounters(mRole); @@ -789,6 +748,7 @@ void Mle::SetStateChild(uint16_t aRloc16) mReattachState = kReattachStop; Get().SetBeaconEnabled(false); mRetxTracker.UpdateOnRoleChangeToChild(); + mPrevRoleRestorer.Stop(); #if OPENTHREAD_FTD if (IsFullThreadDevice()) @@ -1471,14 +1431,6 @@ void Mle::HandleAttachTimer(void) bool shouldAnnounce = true; ParentRequestType type; -#if OPENTHREAD_FTD - if (IsDetached() && mRouterRoleRestorer.IsActive()) - { - mRouterRoleRestorer.HandleTimer(); - ExitNow(); - } -#endif - // First, check if we are waiting to receive parent responses and // found an acceptable parent candidate. @@ -1938,11 +1890,19 @@ Error Mle::SendChildUpdateRequestToParent(ChildUpdateRequestMode aMode) mDelayedSender.RemoveScheduledChildUpdateRequestToParent(); // Track Child Update retx, except when gracefully detaching - // i.e., sending Child Update with zero timeout. + // i.e., sending Child Update with zero timeout, or restoring + // previous child role (re-establishing link with parent). - if (aMode != kAppendZeroTimeout) + switch (aMode) { + case kNormalChildUpdateRequest: + case kAppendChallengeTlv: mRetxTracker.UpdateOnChildUpdateRequestTx(); + break; + + case kAppendZeroTimeout: + case kToRestoreChildRole: + break; } VerifyOrExit((message = NewMleMessage(kCommandChildUpdateRequest)) != nullptr, error = kErrorNoBufs); @@ -5330,6 +5290,148 @@ void Mle::ParentCandidate::CopyTo(Parent &aParent) const aParent = *candidateAsParent; } +//---------------------------------------------------------------------------------------------------------------------- +// PrevRoleRestorer + +Mle::PrevRoleRestorer::PrevRoleRestorer(Instance &aInstance) + : InstanceLocator(aInstance) + , mState(kIdle) + , mAttempts(0) + , mTimer(aInstance) +{ +} + +Error Mle::PrevRoleRestorer::Start(void) +{ + // Starts the process of restoring the device to its previously + // saved role after MLE start. + // + // Returns `kErrorNone` if the restoration process begins + // successfully. An error is returned if: + // - No previous role was saved. + // - The saved role info is inconsistent (e.g., RLOC16 indicates a + // router/leader, but `mLastSavedRole` doesn't match). + + Error error = kErrorFailed; + + mState = kIdle; + mAttempts = 0; + VerifyOrExit(Get().IsDetached()); + VerifyOrExit(Get().GetRloc16() != kInvalidRloc16); + + if (IsRouterRloc16(Get().GetRloc16())) + { +#if OPENTHREAD_FTD + VerifyOrExit((Get().mLastSavedRole == kRoleRouter) || (Get().mLastSavedRole == kRoleLeader)); + VerifyOrExit(Get().IsRouterEligible()); + + Get().SetRxOnWhenIdle(true); + SetState(kRestoringRouterOrLeaderRole); + DetermineMaxLinkRequestAttempts(); + mTimer.Start(Get().GenerateRandomDelay(kMaxStartDelay)); + error = kErrorNone; +#endif + ExitNow(); + } + + VerifyOrExit(Get().mLastSavedRole == kRoleChild); + VerifyOrExit(Get().mParent.IsStateValidOrRestoring()); + SetState(kRestoringChildRole); + mAttempts = kMaxChildUpdatesToRestoreRole; + mTimer.Start(Get().GenerateRandomDelay(kMaxStartDelay)); + error = kErrorNone; + +exit: + return error; +} + +void Mle::PrevRoleRestorer::Stop(void) +{ + SetState(kIdle); + mTimer.Stop(); +} + +void Mle::PrevRoleRestorer::SetState(State aState) +{ + mState = aState; + + if (mState != kIdle) + { + LogInfo("Attempting to restore prev role: %s", RoleToString(Get().mLastSavedRole)); + } +} + +void Mle::PrevRoleRestorer::HandleTimer(void) +{ + VerifyOrExit(mState != kIdle); + + if (!Get().IsDetached()) + { + Stop(); + ExitNow(); + } + + if (mAttempts == 0) + { + LogInfo("Failed to restore prev role"); + Stop(); + IgnoreError(Get().BecomeDetached()); + ExitNow(); + } + + mAttempts--; + +#if OPENTHREAD_FTD + if (mState == kRestoringRouterOrLeaderRole) + { + SendMulticastLinkRequest(); + } + else +#endif + { + SendChildUpdate(); + } + +exit: + return; +} + +void Mle::PrevRoleRestorer::SendChildUpdate(void) +{ + mTimer.Start(Random::NonCrypto::AddJitter(kChildUpdateRetxDelay, kRetxJitter)); + + LogDebg("Sending Child Update Request to restore child role, remaining attempts: %u", mAttempts); + IgnoreError(Get().SendChildUpdateRequestToParent(kToRestoreChildRole)); +} + +#if OPENTHREAD_FTD + +void Mle::PrevRoleRestorer::DetermineMaxLinkRequestAttempts(void) +{ + mAttempts = kMaxCriticalTxCount; + + if ((Get().mLastSavedRole == kRoleRouter) && + (Get().mChildTable.GetNumChildren(Child::kInStateValidOrRestoring) < kMinCriticalChildrenCount)) + { + mAttempts = kMaxTxCount; + } +} + +void Mle::PrevRoleRestorer::SendMulticastLinkRequest(void) +{ + uint32_t delay; + + delay = (mAttempts == 0) ? kLinkRequestTimeout + : Random::NonCrypto::GetUint32InRange(kMulticastRetxDelayMin, kMulticastRetxDelayMax); + + mTimer.Start(delay); + + LogDebg("Sending multicast Link Request to restore role, remaining attempts: %u", mAttempts); + Get().SendLinkRequest(nullptr); +} + +#endif // OPENTHREAD_FTD + //--------------------------------------------------------------------------------------------------------------------- // Detacher diff --git a/src/core/thread/mle.hpp b/src/core/thread/mle.hpp index 3edf36ff0..25b40e206 100644 --- a/src/core/thread/mle.hpp +++ b/src/core/thread/mle.hpp @@ -1356,6 +1356,7 @@ private: kNormalChildUpdateRequest, // Normal Child Update Request. kAppendChallengeTlv, // Append Challenge TLV to Child Update Request even if currently attached. kAppendZeroTimeout, // Use zero timeout when appending Timeout TLV (used for graceful detach). + kToRestoreChildRole, // To restore previous child role (upon restart), re-establishing link with parent. }; enum SecuritySuite : uint8_t @@ -1717,6 +1718,71 @@ private: //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + void HandleRoleRestorerTimer(void) { mPrevRoleRestorer.HandleTimer(); } + + class PrevRoleRestorer : public InstanceLocator + { + // Attempts to restore the device's previously saved role + // (child/router/leader) after an MLE restart (e.g., after a + // device reboot). + // + // If the previous role was router/leader, it sends multicast + // Link Requests. If the role was child, it sends Child + // Update Requests to the parent. It manages message + // retransmissions and stops the restoration attempt if the + // maximum number of attempts is exhausted, setting the + // device to a detached state. + // + // To prevent synchronized transmissions when multiple devices + // reboot at once, it adds a random delay (up to 25 ms) + // before sending the first message. + + public: + PrevRoleRestorer(Instance &aInstance); + + Error Start(void); + void Stop(void); + bool IsRestoringChildRole(void) const { return mState == kRestoringChildRole; } + bool IsRestoringRouterOrLeaderRole(void) const { return mState == kRestoringRouterOrLeaderRole; } + void HandleTimer(void); + +#if OPENTHREAD_FTD + void GenerateRandomChallenge(void) { mChallenge.GenerateRandom(); } + const TxChallenge &GetChallenge(void) const { return mChallenge; } +#endif + + private: + static constexpr uint32_t kMaxStartDelay = 25; + static constexpr uint8_t kMaxChildUpdatesToRestoreRole = kMaxChildKeepAliveAttempts; + static constexpr uint32_t kChildUpdateRetxDelay = kUnicastRetxDelay; /// 1000 msec + static constexpr uint16_t kRetxJitter = 5; + + enum State : uint8_t + { + kIdle, + kRestoringChildRole, + kRestoringRouterOrLeaderRole, + }; + + void SetState(State aState); + void SendChildUpdate(void); +#if OPENTHREAD_FTD + void DetermineMaxLinkRequestAttempts(void); + void SendMulticastLinkRequest(void); +#endif + + using DelayTimer = TimerMilliIn; + + State mState; + uint8_t mAttempts; + DelayTimer mTimer; +#if OPENTHREAD_FTD + TxChallenge mChallenge; +#endif + }; + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + void HandleDetacherTimer(void) { mDetacher.HandleTimer(); } class Detacher : public InstanceLocator @@ -1943,33 +2009,7 @@ private: uint8_t mJitter; }; - //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - class RouterRoleRestorer : public InstanceLocator - { - // Attempts to restore the router or leader role after an MLE - // restart(e.g., after a device reboot) by sending multicast - // Link Requests. - - public: - RouterRoleRestorer(Instance &aInstance); - - bool IsActive(void) const { return mAttempts > 0; } - void Start(DeviceRole aPreviousRole); - void Stop(void) { mAttempts = 0; } - void HandleTimer(void); - - void GenerateRandomChallenge(void) { mChallenge.GenerateRandom(); } - const TxChallenge &GetChallenge(void) const { return mChallenge; } - - private: - void SendMulticastLinkRequest(void); - - uint8_t mAttempts; - TxChallenge mChallenge; - }; - -#endif // OPENTHREAD_FTD +#endif //------------------------------------------------------------------------------------------------------------------ // Methods @@ -2224,17 +2264,18 @@ private: uint32_t mLastAttachTime; uint64_t mLastUpdatedTimestamp; - LeaderData mLeaderData; - Parent mParent; - NeighborTable mNeighborTable; - DelayedSender mDelayedSender; - TxChallenge mParentRequestChallenge; - ParentCandidate mParentCandidate; - MleSocket mSocket; - Counters mCounters; - Detacher mDetacher; - RetxTracker mRetxTracker; - AnnounceHandler mAnnounceHandler; + LeaderData mLeaderData; + Parent mParent; + NeighborTable mNeighborTable; + DelayedSender mDelayedSender; + TxChallenge mParentRequestChallenge; + ParentCandidate mParentCandidate; + MleSocket mSocket; + Counters mCounters; + PrevRoleRestorer mPrevRoleRestorer; + Detacher mDetacher; + RetxTracker mRetxTracker; + AnnounceHandler mAnnounceHandler; #if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE ParentSearch mParentSearch; #endif @@ -2293,7 +2334,6 @@ private: TrickleTimer mAdvertiseTrickleTimer; ChildTable mChildTable; RouterTable mRouterTable; - RouterRoleRestorer mRouterRoleRestorer; RouterRoleTransition mRouterRoleTransition; Ip6::Netif::UnicastAddress mLeaderAloc; #if OPENTHREAD_CONFIG_MLE_DEVICE_PROPERTY_LEADER_WEIGHT_ENABLE diff --git a/src/core/thread/mle_ftd.cpp b/src/core/thread/mle_ftd.cpp index d59da1876..ce2089d05 100644 --- a/src/core/thread/mle_ftd.cpp +++ b/src/core/thread/mle_ftd.cpp @@ -392,6 +392,8 @@ void Mle::SetStateRouterOrLeader(DeviceRole aRole, uint16_t aRloc16, LeaderStart SetRole(aRole); + mPrevRoleRestorer.Stop(); + SetAttachState(kAttachStateIdle); mAttachCounter = 0; mAttachTimer.Stop(); @@ -595,8 +597,8 @@ void Mle::SendLinkRequest(Router *aRouter) if (aRouter == nullptr) { - mRouterRoleRestorer.GenerateRandomChallenge(); - SuccessOrExit(error = message->AppendChallengeTlv(mRouterRoleRestorer.GetChallenge())); + mPrevRoleRestorer.GenerateRandomChallenge(); + SuccessOrExit(error = message->AppendChallengeTlv(mPrevRoleRestorer.GetChallenge())); destination.SetToLinkLocalAllRoutersMulticast(); } else @@ -866,10 +868,9 @@ void Mle::HandleLinkAcceptVariant(RxInfo &aRxInfo, MessageType aMessageType) break; case Neighbor::kStateInvalid: - VerifyOrExit(mRouterRoleRestorer.IsActive() && (response == mRouterRoleRestorer.GetChallenge()), - error = kErrorSecurity); - - OT_FALL_THROUGH; + VerifyOrExit(mPrevRoleRestorer.IsRestoringRouterOrLeaderRole(), error = kErrorSecurity); + VerifyOrExit(response == mPrevRoleRestorer.GetChallenge(), error = kErrorSecurity); + break; case Neighbor::kStateValid: break; @@ -927,7 +928,6 @@ void Mle::HandleLinkAcceptVariant(RxInfo &aRxInfo, MessageType aMessageType) SetStateRouter(GetRloc16()); } - mRouterRoleRestorer.Stop(); mRetrieveNewNetworkData = true; IgnoreError(SendDataRequest(aRxInfo.mMessageInfo.GetPeerAddr())); shouldUpdateRoutes = true; @@ -3929,80 +3929,6 @@ exit: return expired; } -//---------------------------------------------------------------------------------------------------------------------- -// RouterRoleRestorer - -Mle::RouterRoleRestorer::RouterRoleRestorer(Instance &aInstance) - : InstanceLocator(aInstance) - , mAttempts(0) -{ -} - -void Mle::RouterRoleRestorer::Start(DeviceRole aPreviousRole) -{ - // If the device was previously the leader or had more than - // `kMinCriticalChildrenCount` children, we use more link - // request attempts. - - mAttempts = 0; - - switch (aPreviousRole) - { - case kRoleRouter: - if (Get().mChildTable.GetNumChildren(Child::kInStateValidOrRestoring) < kMinCriticalChildrenCount) - { - mAttempts = kMaxTxCount; - break; - } - - OT_FALL_THROUGH; - - case kRoleLeader: - mAttempts = kMaxCriticalTxCount; - break; - - case kRoleChild: - case kRoleDetached: - case kRoleDisabled: - break; - } - - SendMulticastLinkRequest(); -} - -void Mle::RouterRoleRestorer::HandleTimer(void) -{ - if (mAttempts > 0) - { - mAttempts--; - } - - SendMulticastLinkRequest(); -} - -void Mle::RouterRoleRestorer::SendMulticastLinkRequest(void) -{ - uint32_t delay; - - VerifyOrExit(Get().IsDetached(), mAttempts = 0); - - if (mAttempts == 0) - { - IgnoreError(Get().BecomeDetached()); - ExitNow(); - } - - Get().SendLinkRequest(nullptr); - - delay = (mAttempts == 1) ? kLinkRequestTimeout - : Random::NonCrypto::GetUint32InRange(kMulticastRetxDelayMin, kMulticastRetxDelayMax); - - Get().mAttachTimer.Start(delay); - -exit: - return; -} - } // namespace Mle } // namespace ot