diff --git a/include/openthread/instance.h b/include/openthread/instance.h index c12e297a9..40948c152 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -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 diff --git a/include/openthread/thread_ftd.h b/include/openthread/thread_ftd.h index a9e66e924..f5b5838ae 100644 --- a/include/openthread/thread_ftd.h +++ b/include/openthread/thread_ftd.h @@ -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); diff --git a/src/core/api/thread_ftd_api.cpp b/src/core/api/thread_ftd_api.cpp index d838f1770..6cfb7689b 100644 --- a/src/core/api/thread_ftd_api.cpp +++ b/src/core/api/thread_ftd_api.cpp @@ -63,7 +63,7 @@ otError otThreadSetMaxChildIpAddresses(otInstance *aInstance, uint8_t aMaxIpAddr bool otThreadIsRouterEligible(otInstance *aInstance) { - return AsCoreType(aInstance).Get().IsRouterEligible(); + return AsCoreType(aInstance).Get().IsRouterRoleAllowed(); } otError otThreadSetRouterEligible(otInstance *aInstance, bool aEligible) diff --git a/src/core/thread/announce_sender.cpp b/src/core/thread/announce_sender.cpp index 979088555..2acfe87d8 100644 --- a/src/core/thread/announce_sender.cpp +++ b/src/core/thread/announce_sender.cpp @@ -206,7 +206,7 @@ void AnnounceSender::HandleRoleChanged(void) case Mle::kRoleChild: #if OPENTHREAD_FTD - if (Get().IsRouterEligible() && Get().IsRxOnWhenIdle()) + if (Get().IsRouterRoleAllowed() && Get().IsRxOnWhenIdle()) { break; } diff --git a/src/core/thread/mle.cpp b/src/core/thread/mle.cpp index bfc50a5da..d21127af0 100644 --- a/src/core/thread/mle.cpp +++ b/src/core/thread/mle.cpp @@ -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().mLastSavedRole == kRoleRouter) || (Get().mLastSavedRole == kRoleLeader)); - VerifyOrExit(Get().IsRouterEligible()); + VerifyOrExit(Get().IsRouterRoleAllowed()); Get().SetRxOnWhenIdle(true); SetState(kRestoringRouterOrLeaderRole); @@ -5986,7 +5993,7 @@ void Mle::AnnounceHandler::HandleAnnounce(RxInfo &aRxInfo) // back would be pointless. #if OPENTHREAD_FTD - if (Get().IsFullThreadDevice() && Get().IsRouterEligible()) + if (Get().IsFullThreadDevice() && Get().IsRouterRoleAllowed()) { action = kSendAnnouceBack; } diff --git a/src/core/thread/mle.hpp b/src/core/thread/mle.hpp index 4c6994b93..2fc4a5166 100644 --- a/src/core/thread/mle.hpp +++ b/src/core/thread/mle.hpp @@ -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; diff --git a/src/core/thread/mle_ftd.cpp b/src/core/thread/mle_ftd.cpp index dd999ec41..9f242e135 100644 --- a/src/core/thread/mle_ftd.cpp +++ b/src/core/thread/mle_ftd.cpp @@ -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().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().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().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().RegisterReceiver(TimeTicker::kMle); - if (mRouterEligible) - { - Get().SetBeaconEnabled(true); - } + Get().SetBeaconEnabled(IsRouterRoleAllowed()); Get().SubscribeAllRoutersMulticast(); @@ -451,12 +480,14 @@ void Mle::HandleAdvertiseTrickleTimer(TrickleTimer &aTimer) { aTimer.Get(). 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) diff --git a/src/core/thread/network_data_notifier.cpp b/src/core/thread/network_data_notifier.cpp index 898d502b9..b7e3e9ad0 100644 --- a/src/core/thread/network_data_notifier.cpp +++ b/src/core/thread/network_data_notifier.cpp @@ -279,7 +279,7 @@ bool Notifier::IsEligibleForRouterRoleUpgradeAsBorderRouter(void) const uint16_t rloc16 = Get().GetRloc16(); uint8_t activeRouterCount; - VerifyOrExit(Get().IsRouterEligible()); + VerifyOrExit(Get().IsRouterRoleAllowed()); // RouterUpgradeThreshold can be explicitly set to zero in some of // cert tests to disallow device to become router.