mirror of
https://github.com/espressif/openthread.git
synced 2026-06-06 05:24:51 +00:00
[mle] block router downgrade if triggered by child ID request (#12725)
This commit updates the `Mle` router role downgrade logic. When a REED transitions to a router in response to a Child ID Request, it indicates that the attaching child has no other viable parent options. To ensure this child remains connected to the mesh, this commit prevents the newly promoted router from downgrading back to a REED. A new flag `mBlockDowngrade` is added to `Mle`, and a matching property `mBlockParentDowngrade` is added to `Child` to track if it is blocking its parent's downgrade. The downgrade restriction is lifted under specific conditions: when the device detaches, when a new router is added to the network (providing a potential alternative parent for the child), or when all children blocking the downgrade are removed. A new nexus test `test_mle_blocking_downgrade` is added to validate the new behavior.
This commit is contained in:
committed by
GitHub
parent
a5593e7980
commit
9b00c024a8
@@ -336,6 +336,21 @@ public:
|
||||
*/
|
||||
void ResetSecondsSinceLastSupervision(void) { mSecondsSinceSupervision = 0; }
|
||||
|
||||
/**
|
||||
* Indicates whether the child is blocking the parent from downgrading its router role.
|
||||
*
|
||||
* @retval TRUE The child is blocking the parent from downgrading.
|
||||
* @retval FALSE The child is not blocking the parent from downgrading.
|
||||
*/
|
||||
bool IsBlockingParentDowngrade(void) const { return mBlockParentDowngrade; }
|
||||
|
||||
/**
|
||||
* Sets whether the child is blocking the parent from downgrading its router role.
|
||||
*
|
||||
* @param[in] aBlock TRUE to block parent downgrade, FALSE otherwise.
|
||||
*/
|
||||
void SetBlockParentDowngrade(bool aBlock) { mBlockParentDowngrade = aBlock; }
|
||||
|
||||
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
|
||||
/**
|
||||
* Returns if the Child has IPv6 address @p aAddress of MLR state `kMlrStateRegistered`.
|
||||
@@ -376,6 +391,7 @@ private:
|
||||
ChildIp6AddressSet mMlrRegisteredSet;
|
||||
#endif
|
||||
|
||||
bool mBlockParentDowngrade;
|
||||
uint8_t mNetworkDataVersion;
|
||||
|
||||
union
|
||||
|
||||
@@ -81,6 +81,7 @@ Mle::Mle(Instance &aInstance)
|
||||
#endif
|
||||
#if OPENTHREAD_FTD
|
||||
, mRouterEligible(true)
|
||||
, mBlockDowngrade(false)
|
||||
, mAddressSolicitPending(false)
|
||||
, mAddressSolicitRejected(false)
|
||||
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
|
||||
@@ -600,6 +601,7 @@ void Mle::SetStateDetached(void)
|
||||
Get<MeshForwarder>().SetRxOnWhenIdle(true);
|
||||
Get<Mac::Mac>().SetBeaconEnabled(false);
|
||||
#if OPENTHREAD_FTD
|
||||
mBlockDowngrade = false;
|
||||
ClearAlternateRloc16();
|
||||
HandleDetachStart();
|
||||
#endif
|
||||
@@ -1043,6 +1045,20 @@ void Mle::HandleNotifierEvents(Events aEvents)
|
||||
{
|
||||
HandleSecurityPolicyChanged();
|
||||
}
|
||||
|
||||
if (mBlockDowngrade && aEvents.Contains(kEventThreadChildRemoved))
|
||||
{
|
||||
mBlockDowngrade = false;
|
||||
|
||||
for (const Child &child : Get<ChildTable>().Iterate(Child::kInStateValid))
|
||||
{
|
||||
if (child.IsBlockingParentDowngrade())
|
||||
{
|
||||
mBlockDowngrade = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (aEvents.Contains(kEventSupportedChannelMaskChanged))
|
||||
|
||||
+22
-7
@@ -131,6 +131,7 @@ class Mle : public InstanceLocator, private NonCopyable
|
||||
#if OPENTHREAD_FTD
|
||||
friend class ot::TimeTicker;
|
||||
friend class Tmf::Agent;
|
||||
friend class ot::RouterTable;
|
||||
#endif
|
||||
|
||||
public:
|
||||
@@ -870,6 +871,7 @@ public:
|
||||
* @param[in] aPartitionId The preferred Leader Partition Id.
|
||||
*/
|
||||
void SetPreferredLeaderPartitionId(uint32_t aPartitionId) { mPreferredLeaderPartitionId = aPartitionId; }
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
@@ -976,6 +978,24 @@ public:
|
||||
*/
|
||||
void SetRouterDowngradeThreshold(uint8_t aThreshold) { mRouterDowngradeThreshold = aThreshold; }
|
||||
|
||||
/**
|
||||
* Indicates whether or not downgrading from router role to REED is blocked.
|
||||
*
|
||||
* If the transition to the router role was triggered by a Child ID Request, it indicates that the child has no
|
||||
* other parent option. In this case the downgrade is blocked to prevent the parent router becoming REED to ensure
|
||||
* this child remains connected. This flag is cleared in various situations:
|
||||
*
|
||||
* - When device detaches (e.g. partition change).
|
||||
* - If a new router is added (new possible parent).
|
||||
* - If all children blocking downgrade are disconnected.
|
||||
*
|
||||
* This method is intended for testing purposes only.
|
||||
*
|
||||
* @retval TRUE The device is blocked from downgrading.
|
||||
* @retval FALSE The device is not blocked from downgrading.
|
||||
*/
|
||||
bool IsDowngradeBlocked(void) const { return mBlockDowngrade; }
|
||||
|
||||
/**
|
||||
* Returns the MLE_CHILD_ROUTER_LINKS value.
|
||||
*
|
||||
@@ -1120,13 +1140,6 @@ public:
|
||||
*/
|
||||
void ResetAdvertiseInterval(void);
|
||||
|
||||
/**
|
||||
* Updates the MLE Advertisement Trickle timer max interval (if timer is running).
|
||||
*
|
||||
* This is called when there is change in router table.
|
||||
*/
|
||||
void UpdateAdvertiseInterval(void);
|
||||
|
||||
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
|
||||
/**
|
||||
* Generates an MLE Time Synchronization message.
|
||||
@@ -2433,6 +2446,7 @@ private:
|
||||
bool NeighborHasComparableConnectivity(const RouteTlv &aRouteTlv, uint8_t aNeighborId) const;
|
||||
void HandleAdvertiseTrickleTimer(void);
|
||||
void HandleTimeTick(void);
|
||||
void HandleRouterTableEvent(RouterTable::Events aEvents);
|
||||
|
||||
template <Uri kUri> void HandleTmf(Coap::Msg &aMsg);
|
||||
|
||||
@@ -2509,6 +2523,7 @@ private:
|
||||
#if OPENTHREAD_FTD
|
||||
|
||||
bool mRouterEligible : 1;
|
||||
bool mBlockDowngrade : 1;
|
||||
bool mAddressSolicitPending : 1;
|
||||
bool mAddressSolicitRejected : 1;
|
||||
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
|
||||
|
||||
@@ -478,8 +478,15 @@ uint32_t Mle::DetermineAdvertiseIntervalMax(void) const
|
||||
return interval;
|
||||
}
|
||||
|
||||
void Mle::UpdateAdvertiseInterval(void)
|
||||
void Mle::HandleRouterTableEvent(RouterTable::Events aEvents)
|
||||
{
|
||||
// Callback from `RouterTable` when there is a change.
|
||||
|
||||
if (aEvents & RouterTable::kEventRouterAdded)
|
||||
{
|
||||
mBlockDowngrade = false;
|
||||
}
|
||||
|
||||
if (IsRouterOrLeader() && mAdvertiseTrickleTimer.IsRunning())
|
||||
{
|
||||
mAdvertiseTrickleTimer.SetIntervalMax(DetermineAdvertiseIntervalMax());
|
||||
@@ -3420,6 +3427,20 @@ void Mle::HandleAddressSolicitResponse(Coap::Msg *aMsg, Error aResult)
|
||||
for (Child &child : Get<ChildTable>().Iterate(Child::kInStateChildIdRequest))
|
||||
{
|
||||
IgnoreError(SendChildIdResponse(child));
|
||||
|
||||
// The transition to the router role was triggered by a Child
|
||||
// ID Request. This indicates that the child has no other
|
||||
// parent option. We set the flags to prevent the parent
|
||||
// router from downgrading back to a REED to ensure this
|
||||
// child remains connected.
|
||||
//
|
||||
// The `mBlockDowngrade` is cleared in various situations:
|
||||
// - From `SetStateDetached()` (e.g. partition change).
|
||||
// - If a new router is added (new possible parent).
|
||||
// - If all children blocking downgrade are disconnected.
|
||||
|
||||
child.SetBlockParentDowngrade(true);
|
||||
mBlockDowngrade = true;
|
||||
}
|
||||
|
||||
exit:
|
||||
@@ -3724,6 +3745,7 @@ bool Mle::ShouldDowngrade(uint8_t aNeighborId, const RouteTlv &aRouteTlv) const
|
||||
|
||||
VerifyOrExit(IsRouter());
|
||||
VerifyOrExit(mRouterTable.IsAllocated(aNeighborId));
|
||||
VerifyOrExit(!mBlockDowngrade);
|
||||
|
||||
VerifyOrExit(!mRouterRoleTransition.IsPending());
|
||||
|
||||
|
||||
@@ -917,7 +917,7 @@ void RouterTable::HandleTableChanged(void)
|
||||
Get<HistoryTracker::Local>().RecordRouterTableChange();
|
||||
#endif
|
||||
|
||||
Get<Mle::Mle>().UpdateAdvertiseInterval();
|
||||
Get<Mle::Mle>().HandleRouterTableEvent(mEvents);
|
||||
|
||||
mEvents = 0;
|
||||
}
|
||||
|
||||
@@ -271,6 +271,7 @@ ot_nexus_test(border_agent_tracker "core;nexus")
|
||||
ot_nexus_test(discover_scan "core;nexus")
|
||||
ot_nexus_test(dtls "core;nexus")
|
||||
ot_nexus_test(form_join "core;nexus")
|
||||
ot_nexus_test(mle_blocking_downgrade "core;nexus")
|
||||
ot_nexus_test(nat64_translator "core;nexus")
|
||||
ot_nexus_test(srp_lease "core;nexus")
|
||||
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* Copyright (c) 2026, 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.
|
||||
*/
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "platform/nexus_core.hpp"
|
||||
#include "platform/nexus_node.hpp"
|
||||
|
||||
namespace ot {
|
||||
namespace Nexus {
|
||||
|
||||
void TestMleBlockingDowngrade(void)
|
||||
{
|
||||
// This test validates the MLE router role downgrade logic when a REED is
|
||||
// promoted to a router due to a Child ID Request. It verifies that the
|
||||
// newly promoted router blocks its own downgrade to ensure the child remains
|
||||
// connected, and that this block is correctly lifted when the child detaches
|
||||
// or when a new router is introduced to the network (providing an alternative
|
||||
// parent for the child).
|
||||
|
||||
static constexpr uint8_t kNumRouters = 10;
|
||||
|
||||
Core nexus;
|
||||
Node &leader = nexus.CreateNode();
|
||||
Node &dut = nexus.CreateNode();
|
||||
Node &child = nexus.CreateNode();
|
||||
Node &newRouter = nexus.CreateNode();
|
||||
Node *routers[kNumRouters];
|
||||
|
||||
for (Node *&router : routers)
|
||||
{
|
||||
router = &nexus.CreateNode();
|
||||
}
|
||||
|
||||
nexus.AdvanceTime(0);
|
||||
|
||||
for (Node &node : nexus.GetNodes())
|
||||
{
|
||||
node.GetInstance().SetLogLevel(kLogLevelNone);
|
||||
}
|
||||
|
||||
Log("---------------------------------------------------------------------------------------");
|
||||
Log("Form initial topology");
|
||||
|
||||
leader.AllowList(dut);
|
||||
dut.AllowList(leader);
|
||||
|
||||
leader.AllowList(newRouter);
|
||||
newRouter.AllowList(leader);
|
||||
|
||||
child.AllowList(dut);
|
||||
dut.AllowList(child);
|
||||
|
||||
child.AllowList(newRouter);
|
||||
newRouter.AllowList(child);
|
||||
|
||||
for (Node *router : routers)
|
||||
{
|
||||
leader.AllowList(*router);
|
||||
router->AllowList(leader);
|
||||
|
||||
dut.AllowList(*router);
|
||||
router->AllowList(dut);
|
||||
|
||||
newRouter.AllowList(*router);
|
||||
router->AllowList(newRouter);
|
||||
}
|
||||
|
||||
leader.Form();
|
||||
nexus.AdvanceTime(13 * Time::kOneSecondInMsec);
|
||||
VerifyOrQuit(leader.Get<Mle::Mle>().IsLeader());
|
||||
|
||||
for (Node *router : routers)
|
||||
{
|
||||
router->Join(leader);
|
||||
}
|
||||
|
||||
nexus.AdvanceTime(300 * Time::kOneSecondInMsec);
|
||||
|
||||
VerifyOrQuit(leader.Get<Mle::Mle>().IsLeader());
|
||||
|
||||
for (Node *router : routers)
|
||||
{
|
||||
VerifyOrQuit(router->Get<Mle::Mle>().IsRouter());
|
||||
}
|
||||
|
||||
Log("---------------------------------------------------------------------------------------");
|
||||
Log("Setup DUT - Validate it stays as a REED");
|
||||
|
||||
dut.Get<Mle::Mle>().SetRouterUpgradeThreshold(3);
|
||||
dut.Get<Mle::Mle>().SetRouterDowngradeThreshold(4);
|
||||
|
||||
VerifyOrQuit(!dut.Get<Mle::Mle>().IsDowngradeBlocked());
|
||||
|
||||
dut.Join(leader);
|
||||
|
||||
nexus.AdvanceTime(300 * Time::kOneSecondInMsec);
|
||||
|
||||
VerifyOrQuit(dut.Get<Mle::Mle>().IsChild());
|
||||
VerifyOrQuit(!dut.Get<Mle::Mle>().IsDowngradeBlocked());
|
||||
|
||||
Log("---------------------------------------------------------------------------------------");
|
||||
Log("Attach `child` only connecting through the DUT");
|
||||
|
||||
child.Join(dut, Node::kAsFed);
|
||||
|
||||
nexus.AdvanceTime(30 * Time::kOneSecondInMsec);
|
||||
|
||||
VerifyOrQuit(child.Get<Mle::Mle>().IsChild());
|
||||
VerifyOrQuit(child.Get<Mle::Mle>().GetParent().GetExtAddress() == dut.Get<Mac::Mac>().GetExtAddress());
|
||||
|
||||
Log("---------------------------------------------------------------------------------------");
|
||||
Log("Validate that DUT is promoted to router and blocking downgrade");
|
||||
|
||||
VerifyOrQuit(dut.Get<Mle::Mle>().IsRouter());
|
||||
VerifyOrQuit(dut.Get<Mle::Mle>().IsDowngradeBlocked());
|
||||
|
||||
nexus.AdvanceTime(10 * Time::kOneMinuteInMsec);
|
||||
|
||||
VerifyOrQuit(dut.Get<Mle::Mle>().IsRouter());
|
||||
VerifyOrQuit(dut.Get<Mle::Mle>().IsDowngradeBlocked());
|
||||
|
||||
Log("---------------------------------------------------------------------------------------");
|
||||
Log("Disconnect one of the routers - validate that DUT still blocks downgrade");
|
||||
|
||||
routers[0]->Get<Mle::Mle>().Stop();
|
||||
routers[0]->Get<ThreadNetif>().Down();
|
||||
|
||||
nexus.AdvanceTime(10);
|
||||
|
||||
VerifyOrQuit(routers[0]->Get<Mle::Mle>().IsDisabled());
|
||||
|
||||
nexus.AdvanceTime(10 * Time::kOneMinuteInMsec);
|
||||
|
||||
VerifyOrQuit(dut.Get<Mle::Mle>().IsRouter());
|
||||
VerifyOrQuit(dut.Get<Mle::Mle>().IsDowngradeBlocked());
|
||||
VerifyOrQuit(child.Get<Mle::Mle>().IsChild());
|
||||
|
||||
Log("---------------------------------------------------------------------------------------");
|
||||
Log("Disconnect the child - validate that DUT can now downgrade");
|
||||
|
||||
child.Get<Mle::Mle>().Stop();
|
||||
child.Get<ThreadNetif>().Down();
|
||||
|
||||
nexus.AdvanceTime(10);
|
||||
|
||||
VerifyOrQuit(child.Get<Mle::Mle>().IsDisabled());
|
||||
|
||||
nexus.AdvanceTime(10 * Time::kOneMinuteInMsec);
|
||||
|
||||
VerifyOrQuit(!dut.Get<Mle::Mle>().IsDowngradeBlocked());
|
||||
VerifyOrQuit(dut.Get<Mle::Mle>().IsChild());
|
||||
|
||||
Log("---------------------------------------------------------------------------------------");
|
||||
Log("Reconnect the child - validate that DUT once again disallow downgrade");
|
||||
|
||||
child.Get<ThreadNetif>().Up();
|
||||
SuccessOrQuit(child.Get<Mle::Mle>().Start());
|
||||
|
||||
nexus.AdvanceTime(30 * Time::kOneSecondInMsec);
|
||||
|
||||
VerifyOrQuit(child.Get<Mle::Mle>().IsChild());
|
||||
|
||||
VerifyOrQuit(dut.Get<Mle::Mle>().IsRouter());
|
||||
VerifyOrQuit(dut.Get<Mle::Mle>().IsDowngradeBlocked());
|
||||
|
||||
nexus.AdvanceTime(10 * Time::kOneMinuteInMsec);
|
||||
|
||||
VerifyOrQuit(dut.Get<Mle::Mle>().IsRouter());
|
||||
VerifyOrQuit(dut.Get<Mle::Mle>().IsDowngradeBlocked());
|
||||
|
||||
Log("---------------------------------------------------------------------------------------");
|
||||
Log("Introduce `newRouter` as a new router - validate DUT now allows downgrade");
|
||||
|
||||
newRouter.Join(leader);
|
||||
|
||||
nexus.AdvanceTime(15 * Time::kOneMinuteInMsec);
|
||||
|
||||
VerifyOrQuit(newRouter.Get<Mle::Mle>().IsRouter());
|
||||
VerifyOrQuit(!dut.Get<Mle::Mle>().IsDowngradeBlocked());
|
||||
|
||||
Log("---------------------------------------------------------------------------------------");
|
||||
Log("Validate that DUT is downgraded to REED and child connected to the new router");
|
||||
|
||||
VerifyOrQuit(dut.Get<Mle::Mle>().IsChild());
|
||||
|
||||
VerifyOrQuit(child.Get<Mle::Mle>().IsChild());
|
||||
VerifyOrQuit(child.Get<Mle::Mle>().GetParent().GetExtAddress() == newRouter.Get<Mac::Mac>().GetExtAddress());
|
||||
}
|
||||
|
||||
} // namespace Nexus
|
||||
} // namespace ot
|
||||
|
||||
int main(void)
|
||||
{
|
||||
ot::Nexus::TestMleBlockingDowngrade();
|
||||
printf("All tests passed\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user