[mle] track "router role allowed" state in a new variable (#12854)

This commit introduces a new member variable `mRouterRoleAllowed` in
the `Mle` class to cache the evaluation of whether the device is
currently permitted to operate as a router.

Previously, the `IsRouterEligible()` method evaluated several
conditions (e.g., `IsFullThreadDevice()`, `mRouterEligible` config,
and various fields in `SecurityPolicy`) every time it was called.
Since this method is invoked frequently across different `Mle`
operations, re-evaluating these conditions repeatedly was
inefficient.

The new `mRouterRoleAllowed` variable caches the final computed
result. It is updated via the `UpdateRouterRoleAllowed()` method
whenever any underlying input changes, such as:
- `Mle` starting.
- Configuration parameter updates (e.g., `SetRouterEligible()`).
- Security policy changes from the `KeyManager`.

This change centralizes the logic for handling role permission updates
into a single location (`UpdateRouterRoleAllowed()`). By
consolidating the actions taken when the allowed state changes, the
codebase is cleaner and easier to maintain and update.

It also provides a clearer conceptual distinction between the user's
router configuration (`mRouterEligible`) and the effective state
used by the device.
This commit is contained in:
Abtin Keshavarzian
2026-04-14 14:15:22 -05:00
committed by GitHub
parent 582dc1cd67
commit 221a9cbbb0
8 changed files with 145 additions and 89 deletions
+1 -1
View File
@@ -52,7 +52,7 @@ extern "C" {
*
* @note This number versions both OpenThread platform and user APIs.
*/
#define OPENTHREAD_API_VERSION (590)
#define OPENTHREAD_API_VERSION (591)
/**
* @addtogroup api-instance
+6 -3
View File
@@ -153,12 +153,15 @@ uint16_t otThreadGetMaxAllowedChildren(otInstance *aInstance);
otError otThreadSetMaxAllowedChildren(otInstance *aInstance, uint16_t aMaxChildren);
/**
* Indicates whether or not the device is router-eligible.
* Indicates whether or not the device is allowed to take router or leader roles.
*
* A device is allowed to become a router if it is a Full Thread Device (FTD), is currently configured to be
* router-eligible (see `otThreadSetRouterEligible(true)`), and the active Security Policy permits routers.
*
* @param[in] aInstance A pointer to an OpenThread instance.
*
* @retval TRUE If device is router-eligible.
* @retval FALSE If device is not router-eligible.
* @retval TRUE If the router role is allowed.
* @retval FALSE If the router role is not allowed.
*/
bool otThreadIsRouterEligible(otInstance *aInstance);
+1 -1
View File
@@ -63,7 +63,7 @@ otError otThreadSetMaxChildIpAddresses(otInstance *aInstance, uint8_t aMaxIpAddr
bool otThreadIsRouterEligible(otInstance *aInstance)
{
return AsCoreType(aInstance).Get<Mle::Mle>().IsRouterEligible();
return AsCoreType(aInstance).Get<Mle::Mle>().IsRouterRoleAllowed();
}
otError otThreadSetRouterEligible(otInstance *aInstance, bool aEligible)
+1 -1
View File
@@ -206,7 +206,7 @@ void AnnounceSender::HandleRoleChanged(void)
case Mle::kRoleChild:
#if OPENTHREAD_FTD
if (Get<Mle::Mle>().IsRouterEligible() && Get<Mle::Mle>().IsRxOnWhenIdle())
if (Get<Mle::Mle>().IsRouterRoleAllowed() && Get<Mle::Mle>().IsRxOnWhenIdle())
{
break;
}
+10 -3
View File
@@ -81,6 +81,7 @@ Mle::Mle(Instance &aInstance)
#endif
#if OPENTHREAD_FTD
, mRouterEligible(true)
, mRouterRoleAllowed(true)
, mBlockDowngrade(false)
, mAddressSolicitPending(false)
, mAddressSolicitRejected(false)
@@ -453,6 +454,10 @@ void Mle::Restore(void)
// non-volatile settings after boot.
mHasRestored = true;
#if OPENTHREAD_FTD
UpdateRouterRoleAllowed(kReasonMleInit);
#endif
exit:
return;
}
@@ -721,6 +726,8 @@ Error Mle::SetDeviceMode(DeviceMode aDeviceMode)
{
ClearAlternateRloc16();
}
UpdateRouterRoleAllowed(kReasonDeviceModeChanged);
#endif
if (IsAttached())
@@ -1045,7 +1052,7 @@ void Mle::HandleNotifierEvents(Events aEvents)
#if OPENTHREAD_FTD
if (aEvents.Contains(kEventSecurityPolicyChanged))
{
HandleSecurityPolicyChanged();
UpdateRouterRoleAllowed(kReasonSecurityPolicyChanged);
}
if (mBlockDowngrade && aEvents.Contains(kEventThreadChildRemoved))
@@ -4313,7 +4320,7 @@ Error Mle::PrevRoleRestorer::Start(void)
{
#if OPENTHREAD_FTD
VerifyOrExit((Get<Mle>().mLastSavedRole == kRoleRouter) || (Get<Mle>().mLastSavedRole == kRoleLeader));
VerifyOrExit(Get<Mle>().IsRouterEligible());
VerifyOrExit(Get<Mle>().IsRouterRoleAllowed());
Get<MeshForwarder>().SetRxOnWhenIdle(true);
SetState(kRestoringRouterOrLeaderRole);
@@ -5986,7 +5993,7 @@ void Mle::AnnounceHandler::HandleAnnounce(RxInfo &aRxInfo)
// back would be pointless.
#if OPENTHREAD_FTD
if (Get<Mle>().IsFullThreadDevice() && Get<Mle>().IsRouterEligible())
if (Get<Mle>().IsFullThreadDevice() && Get<Mle>().IsRouterRoleAllowed())
{
action = kSendAnnouceBack;
}
+26 -11
View File
@@ -760,14 +760,6 @@ public:
#endif // OPENTHREAD_CONFIG_WAKEUP_COORDINATOR_ENABLE
#if OPENTHREAD_FTD
/**
* Indicates whether or not the device is router-eligible.
*
* @retval true If device is router-eligible.
* @retval false If device is not router-eligible.
*/
bool IsRouterEligible(void) const;
/**
* Sets whether or not the device is router-eligible.
*
@@ -781,6 +773,17 @@ public:
*/
Error SetRouterEligible(bool aEligible);
/**
* Indicates whether the router role is currently allowed.
*
* A device is allowed to become a router if it is a Full Thread Device (FTD), is currently configured to be
* router-eligible (see `SetRouterEligible(true)`), and the active Security Policy permits routers.
*
* @retval TRUE If the router role is allowed.
* @retval FALSE If the router role is not allowed.
*/
bool IsRouterRoleAllowed(void) const { return mRouterRoleAllowed; }
/**
* Indicates whether a node is the only router on the network.
*
@@ -1176,14 +1179,14 @@ public:
*
* @param[in] aEnabled TRUE if the device was commissioned using CCM, FALSE otherwise.
*/
void SetCcmEnabled(bool aEnabled) { mCcmEnabled = aEnabled; }
void SetCcmEnabled(bool aEnabled);
/**
* Sets whether the Security Policy TLV version-threshold for routing (VR field) is enabled.
*
* @param[in] aEnabled TRUE to enable Security Policy TLV version-threshold for routing, FALSE otherwise.
*/
void SetThreadVersionCheckEnabled(bool aEnabled) { mThreadVersionCheckEnabled = aEnabled; }
void SetThreadVersionCheckEnabled(bool aEnabled);
/**
* Gets the current Interval Max value used by Advertisement trickle timer.
@@ -1471,6 +1474,16 @@ private:
kAddrSolicitUnrecognizedReason = 6,
};
#if OPENTHREAD_FTD
enum UpdateRouterRoleAllowedReason : uint8_t // Used in `UpdateRouterRoleAllowed()`
{
kReasonMleInit,
kReasonDeviceModeChanged,
kReasonConfigParameterChanged,
kReasonSecurityPolicyChanged,
};
#endif
enum MessageAction : uint8_t
{
kMessageSend,
@@ -2404,10 +2417,11 @@ private:
void ClearAlternateRloc16(void);
uint8_t SelectLeaderId(void) const;
uint32_t SelectPartitionId(void) const;
bool DetermineIfRouterRoleAllowed(void) const;
void UpdateRouterRoleAllowed(UpdateRouterRoleAllowedReason aReason);
void DetermineConnectivity(Connectivity &aConnectivity) const;
void HandleDetachStart(void);
void HandleChildStart(void);
void HandleSecurityPolicyChanged(void);
void HandleLinkRequest(RxInfo &aRxInfo);
void HandleLinkAccept(RxInfo &aRxInfo);
void HandleLinkAcceptAndRequest(RxInfo &aRxInfo);
@@ -2533,6 +2547,7 @@ private:
#if OPENTHREAD_FTD
bool mRouterEligible : 1;
bool mRouterRoleAllowed : 1;
bool mBlockDowngrade : 1;
bool mAddressSolicitPending : 1;
bool mAddressSolicitRejected : 1;
+99 -68
View File
@@ -76,9 +76,9 @@ void Mle::HandlePartitionChange(void)
mRouterTable.Clear();
}
bool Mle::IsRouterEligible(void) const
bool Mle::DetermineIfRouterRoleAllowed(void) const
{
bool rval = false;
bool allowed = false;
const SecurityPolicy &secPolicy = Get<KeyManager>().GetSecurityPolicy();
VerifyOrExit(mRouterEligible && IsFullThreadDevice());
@@ -86,6 +86,7 @@ bool Mle::IsRouterEligible(void) const
#if OPENTHREAD_CONFIG_THREAD_VERSION == OT_THREAD_VERSION_1_1
VerifyOrExit(secPolicy.mRoutersEnabled);
#else
if (secPolicy.mCommercialCommissioningEnabled)
{
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
@@ -94,6 +95,7 @@ bool Mle::IsRouterEligible(void) const
VerifyOrExit(secPolicy.mNonCcmRoutersEnabled);
#endif
}
if (!secPolicy.mRoutersEnabled)
{
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
@@ -105,12 +107,73 @@ bool Mle::IsRouterEligible(void) const
kThreadVersion);
#endif
}
#endif
rval = true;
#endif // #if OPENTHREAD_CONFIG_THREAD_VERSION == OT_THREAD_VERSION_1_1
allowed = true;
exit:
return rval;
return allowed;
}
void Mle::UpdateRouterRoleAllowed(UpdateRouterRoleAllowedReason aReason)
{
bool allowed = DetermineIfRouterRoleAllowed();
VerifyOrExit(allowed != mRouterRoleAllowed);
mRouterRoleAllowed = allowed;
if (IsAttached())
{
Get<Mac::Mac>().SetBeaconEnabled(mRouterRoleAllowed);
}
// Take action based on the current role, the new `mRouterRoleAllowed`,
// and the reason for the change.
if (IsChild() && mRouterRoleAllowed && (aReason == kReasonConfigParameterChanged))
{
mRouterRoleTransition.StartTimeout();
}
if (IsRouterOrLeader())
{
// If currently acting as router or leader, but the config or
// security policy changes such that the router role is no
// longer allowed, we take action based on the reason. If the
// change is due to a security policy update, we start a jitter
// timeout to downgrade (per Section 5.9.9 in the Thread spec),
// adding an extra delay if acting as leader. If this is
// triggered due to parameter change (`SetRouterEligible(false)`
// was called by the user), we take immediate action and become
// detached.
VerifyOrExit(!mRouterRoleAllowed);
switch (aReason)
{
case kReasonMleInit:
case kReasonDeviceModeChanged:
break;
case kReasonConfigParameterChanged:
IgnoreError(BecomeDetached());
break;
case kReasonSecurityPolicyChanged:
VerifyOrExit(!mRouterRoleTransition.IsPending());
mRouterRoleTransition.StartTimeout();
if (IsLeader())
{
mRouterRoleTransition.IncreaseTimeout(kLeaderDowngradeExtraDelay);
}
break;
}
}
exit:
return;
}
Error Mle::SetRouterEligible(bool aEligible)
@@ -125,57 +188,26 @@ Error Mle::SetRouterEligible(bool aEligible)
VerifyOrExit(aEligible != mRouterEligible);
mRouterEligible = aEligible;
switch (mRole)
{
case kRoleDisabled:
case kRoleDetached:
break;
case kRoleChild:
if (mRouterEligible)
{
mRouterRoleTransition.StartTimeout();
}
Get<Mac::Mac>().SetBeaconEnabled(mRouterEligible);
break;
case kRoleRouter:
case kRoleLeader:
if (!mRouterEligible)
{
IgnoreError(BecomeDetached());
}
break;
}
UpdateRouterRoleAllowed(kReasonConfigParameterChanged);
exit:
return error;
}
void Mle::HandleSecurityPolicyChanged(void)
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
void Mle::SetCcmEnabled(bool aEnabled)
{
// If we are currently router or leader and no longer eligible to
// be a router (due to security policy change), we start jitter
// timeout to downgrade.
VerifyOrExit(IsRouterOrLeader() && !IsRouterEligible());
VerifyOrExit(!mRouterRoleTransition.IsPending());
mRouterRoleTransition.StartTimeout();
if (IsLeader())
{
mRouterRoleTransition.IncreaseTimeout(kLeaderDowngradeExtraDelay);
}
exit:
return;
mCcmEnabled = aEnabled;
UpdateRouterRoleAllowed(kReasonConfigParameterChanged);
}
void Mle::SetThreadVersionCheckEnabled(bool aEnabled)
{
mThreadVersionCheckEnabled = aEnabled;
UpdateRouterRoleAllowed(kReasonConfigParameterChanged);
}
#endif
#if OPENTHREAD_CONFIG_MLE_DEVICE_PROPERTY_LEADER_WEIGHT_ENABLE
void Mle::SetDeviceProperties(const DeviceProperties &aDeviceProperties)
{
@@ -204,7 +236,7 @@ Error Mle::BecomeRouter(RouterUpgradeReason aReason)
ExitNow();
}
VerifyOrExit(IsRouterEligible(), error = kErrorNotCapable);
VerifyOrExit(IsRouterRoleAllowed(), error = kErrorNotCapable);
LogInfo("Attempt to become router, reason:%s", RouterUpgradeReasonToString(aReason));
@@ -230,7 +262,7 @@ Error Mle::BecomeLeader(LeaderWeightCheck aMode)
#endif
VerifyOrExit(!IsDisabled(), error = kErrorInvalidState);
VerifyOrExit(!IsLeader(), error = kErrorNone);
VerifyOrExit(IsRouterEligible(), error = kErrorNotCapable);
VerifyOrExit(IsRouterRoleAllowed(), error = kErrorNotCapable);
if ((aMode == kCheckLeaderWeight) && IsAttached())
{
@@ -314,10 +346,7 @@ void Mle::HandleChildStart(void)
StopLeader();
Get<TimeTicker>().RegisterReceiver(TimeTicker::kMle);
if (mRouterEligible)
{
Get<Mac::Mac>().SetBeaconEnabled(true);
}
Get<Mac::Mac>().SetBeaconEnabled(IsRouterRoleAllowed());
Get<ThreadNetif>().SubscribeAllRoutersMulticast();
@@ -451,12 +480,14 @@ void Mle::HandleAdvertiseTrickleTimer(TrickleTimer &aTimer) { aTimer.Get<Mle>().
void Mle::HandleAdvertiseTrickleTimer(void)
{
VerifyOrExit(IsRouterEligible(), mAdvertiseTrickleTimer.Stop());
SendMulticastAdvertisement();
exit:
return;
if (!IsRouterRoleAllowed())
{
mAdvertiseTrickleTimer.Stop();
}
else
{
SendMulticastAdvertisement();
}
}
void Mle::StopAdvertiseTrickleTimer(void) { mAdvertiseTrickleTimer.Stop(); }
@@ -1128,7 +1159,7 @@ bool Mle::IsSingleton(void) const
{
bool isSingleton = true;
VerifyOrExit(IsAttached() && IsRouterEligible());
VerifyOrExit(IsAttached() && IsRouterRoleAllowed());
isSingleton = (mRouterTable.GetActiveRouterCount() <= 1);
exit:
@@ -1421,7 +1452,7 @@ void Mle::HandleParentRequest(RxInfo &aRxInfo)
Log(kMessageReceive, kTypeParentRequest, aRxInfo.mMessageInfo.GetPeerAddr());
VerifyOrExit(IsRouterEligible());
VerifyOrExit(IsRouterRoleAllowed());
VerifyOrExit(!IsDetached() && !IsAttaching());
VerifyOrExit(!mDetacher.IsDetaching());
@@ -1590,9 +1621,9 @@ void Mle::HandleTimeTick(void)
OT_FALL_THROUGH;
case kRoleLeader:
if (!IsRouterEligible())
if (!IsRouterRoleAllowed())
{
LogInfo("No longer router eligible");
LogInfo("Router role no longer allowed");
IgnoreError(BecomeDetached());
}
@@ -1611,7 +1642,7 @@ void Mle::HandleTimeTick(void)
break;
case kRoleChild:
if (!IsRouterEligible())
if (!IsRouterRoleAllowed())
{
break;
}
@@ -2084,7 +2115,7 @@ void Mle::HandleChildIdRequest(RxInfo &aRxInfo)
Log(kMessageReceive, kTypeChildIdRequest, aRxInfo.mMessageInfo.GetPeerAddr());
VerifyOrExit(IsRouterEligible(), error = kErrorInvalidState);
VerifyOrExit(IsRouterRoleAllowed(), error = kErrorInvalidState);
VerifyOrExit(IsAttached(), error = kErrorInvalidState);
@@ -2732,7 +2763,7 @@ void Mle::HandleDiscoveryRequest(RxInfo &aRxInfo)
Log(kMessageReceive, kTypeDiscoveryRequest, aRxInfo.mMessageInfo.GetPeerAddr());
VerifyOrExit(IsRouterEligible(), error = kErrorInvalidState);
VerifyOrExit(IsRouterRoleAllowed(), error = kErrorInvalidState);
SuccessOrExit(error = Tlv::FindTlvValueOffsetRange(aRxInfo.mMessage, Tlv::kDiscovery, offsetRange));
@@ -3473,7 +3504,7 @@ bool Mle::WillBecomeRouterSoon(void) const
bool willBecomeRouter = false;
VerifyOrExit(IsRouterEligible() && IsChild());
VerifyOrExit(IsRouterRoleAllowed() && IsChild());
VerifyOrExit(!mAddressSolicitRejected);
if (!mAddressSolicitPending)
+1 -1
View File
@@ -279,7 +279,7 @@ bool Notifier::IsEligibleForRouterRoleUpgradeAsBorderRouter(void) const
uint16_t rloc16 = Get<Mle::Mle>().GetRloc16();
uint8_t activeRouterCount;
VerifyOrExit(Get<Mle::Mle>().IsRouterEligible());
VerifyOrExit(Get<Mle::Mle>().IsRouterRoleAllowed());
// RouterUpgradeThreshold can be explicitly set to zero in some of
// cert tests to disallow device to become router.