[mle] use increasing timeout for child role restoration (#11895)

This change updates the `PrevRoleRestorer` logic to use an increasing
timeout when a non-sleepy device sends `Child Update Request`
messages to restore its previous child role.

The timeout starts at 4 seconds and doubles with each subsequent
retransmission. This strategy is designed to handle scenarios where
the parent may also be restarting, such as after a network-wide power
outage, by allowing more time for the parent to recover. Over four
attempts, the device waits a total of 29 seconds (4 + 8 + 16 + 1)
before abandoning the restoration process.

Sleepy devices continue to use a short and fixed 1-second timeout
between retransmissions.

Additionally, if the restoring child receives a Child Update Request
from its former parent, it switches back to the shorter 1-second
timeout to expedite the restoration process and allow at least
two more Child Update attempts.
This commit is contained in:
Abtin Keshavarzian
2026-03-11 16:21:44 -05:00
committed by GitHub
parent 69d4282a3f
commit 7696e38945
2 changed files with 83 additions and 7 deletions
+70 -2
View File
@@ -2231,6 +2231,7 @@ void Mle::HandleChildUpdateRequestOnChild(RxInfo &aRxInfo)
// message (`canTrustMessage` will be `false`).
VerifyOrExit(mPrevRoleRestorer.IsRestoringChildRole());
mPrevRoleRestorer.HandleChildUpdateRequest(aRxInfo);
}
SuccessOrExit(error = Tlv::Find<SourceAddressTlv>(aRxInfo.mMessage, sourceAddress));
@@ -4203,6 +4204,8 @@ Mle::PrevRoleRestorer::PrevRoleRestorer(Instance &aInstance)
: InstanceLocator(aInstance)
, mState(kIdle)
, mAttempts(0)
, mUseIncreasingTimeout(false)
, mCurTimeout(0)
, mTimer(aInstance)
{
}
@@ -4242,9 +4245,38 @@ Error Mle::PrevRoleRestorer::Start(void)
VerifyOrExit(Get<Mle>().mLastSavedRole == kRoleChild);
VerifyOrExit(Get<Mle>().mParent.IsStateValidOrRestoring());
// Try to restore the previous child role by sending up to
// `kChildUpdateAttempts` Child Update Requests to the former
// parent.
//
// A non-sleepy child uses an increasing timeout (starting at
// `kChildUpdateStartTimeout` and doubling) to give a potentially
// restarting parent more time to recover. The total wait time is
// 29 seconds (4+8+16+1). A sleepy child uses a fixed short
// timeout of one seconds(`kChildUpdateMinTimeout`).
//
// Receiving an MLE message from the parent triggers a switch to
// the short timeout and guarantees at least two more attempts
// (`kExtraChildUpdatesAfterRxFromParent`).
SetState(kRestoringChildRole);
GenerateRandomChallenge();
mAttempts = kMaxChildUpdatesToRestoreRole;
mAttempts = kChildUpdateAttempts;
if (!Get<Mle>().IsRxOnWhenIdle())
{
mUseIncreasingTimeout = false;
mCurTimeout = kChildUpdateMinTimeout;
}
else
{
mUseIncreasingTimeout = true;
mCurTimeout = kChildUpdateStartTimeout;
}
mTimer.Start(Get<Mle>().GenerateRandomDelay(kMaxStartDelay));
error = kErrorNone;
@@ -4305,12 +4337,48 @@ exit:
void Mle::PrevRoleRestorer::SendChildUpdate(void)
{
mTimer.Start(Random::NonCrypto::AddJitter(kChildUpdateRetxDelay, kRetxJitter));
if (mAttempts == 0)
{
mTimer.Start(kChildUpdateMinTimeout);
}
else
{
mTimer.Start(Random::NonCrypto::AddJitter(mCurTimeout, kChildUpdateRetxJitter));
if (mUseIncreasingTimeout)
{
mCurTimeout += mCurTimeout;
}
}
LogDebg("Sending Child Update Request to restore child role, remaining attempts: %u", mAttempts);
IgnoreError(Get<Mle>().SendChildUpdateRequestToParent(kToRestoreChildRole));
}
void Mle::PrevRoleRestorer::CheckIfMessageIsFromParent(RxInfo &aRxInfo)
{
VerifyOrExit(IsRestoringChildRole());
VerifyOrExit(aRxInfo.mNeighbor == &Get<Mle>().GetParent());
VerifyOrExit(mUseIncreasingTimeout);
LogInfo("Received msg from former parent, speeding up child role restoration");
mUseIncreasingTimeout = false;
mCurTimeout = kChildUpdateMinTimeout;
if (mAttempts <= kExtraChildUpdatesAfterRxFromParent)
{
mAttempts = kExtraChildUpdatesAfterRxFromParent;
LogInfo("Allow extra Child Update attempts %u", mAttempts);
}
mTimer.FireAtIfEarlier(TimerMilli::GetNow() + Random::NonCrypto::AddJitter(mCurTimeout, kChildUpdateRetxJitter));
exit:
return;
}
#if OPENTHREAD_FTD
void Mle::PrevRoleRestorer::DetermineMaxLinkRequestAttempts(void)
+13 -5
View File
@@ -1843,15 +1843,20 @@ private:
bool IsRestoringChildRole(void) const { return mState == kRestoringChildRole; }
bool IsRestoringRouterOrLeaderRole(void) const { return mState == kRestoringRouterOrLeaderRole; }
void HandleTimer(void);
void HandleChildUpdateRequest(RxInfo &aRxInfo) { CheckIfMessageIsFromParent(aRxInfo); }
void GenerateRandomChallenge(void) { mChallenge.GenerateRandom(); }
const TxChallenge &GetChallenge(void) const { return mChallenge; }
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;
static constexpr uint32_t kMaxStartDelay = 25; // in msec
// Restoring Child Role (sending "child update request").
static constexpr uint8_t kChildUpdateAttempts = 4;
static constexpr uint8_t kExtraChildUpdatesAfterRxFromParent = 2;
static constexpr uint16_t kChildUpdateMinTimeout = 1000; // in ms
static constexpr uint16_t kChildUpdateStartTimeout = 4000; // in ms
static constexpr uint16_t kChildUpdateRetxJitter = 25; // in ms
enum State : uint8_t
{
@@ -1862,6 +1867,7 @@ private:
void SetState(State aState);
void SendChildUpdate(void);
void CheckIfMessageIsFromParent(RxInfo &aRxInfo);
#if OPENTHREAD_FTD
void DetermineMaxLinkRequestAttempts(void);
void SendMulticastLinkRequest(void);
@@ -1870,7 +1876,9 @@ private:
using DelayTimer = TimerMilliIn<Mle, &Mle::HandleRoleRestorerTimer>;
State mState;
uint8_t mAttempts;
uint8_t mAttempts : 7;
bool mUseIncreasingTimeout : 1;
uint16_t mCurTimeout;
DelayTimer mTimer;
TxChallenge mChallenge;
};