mirror of
https://github.com/espressif/openthread.git
synced 2026-08-21 01:49:52 +00:00
[mle] implement periodic parent search mechanism on FED/REED (#10722)
This commit implements "Periodic Parent Search" mechanism for FED/REED devices (FTD children). This enhances and builds upon the existing parent search feature, which is mainly intended for MTD children. An FTD child receives and processes MLE Advertisements from neighboring routers. The child uses this information to track the one-way link quality to each router, which is later used to compare and select potential new parents. Every "parent search check interval", the FTD child checks to see if it can select a better parent by evaluating all neighboring routers based on their link quality information. A router is considered a suitable parent candidate only if its average RSS exceeds the current parent's RSS by a margin (`PARENT_SEARCH_RSS_MARGIN`). Once a candidate is selected, the FTD child sends unicast MLE Parent Requests to both the candidate and its current parent. This ensures updated connectivity information is obtained from both before making a decision. The same set of criteria used to compare candidates during initial attach are applied during parent switch. If the attach attempt to the selected candidate fails (e.g., the router already has the maximum number of children it can support), the FTD child ensures not to select the same router again until a "reselect timeout" expires. This commit also adds the `test-025-fed-parent-search.py test`, which validates the newly added FTD parent search behavior, including the mechanisms to attach to a selected router and the reselect timeout.
This commit is contained in:
@@ -52,7 +52,7 @@ extern "C" {
|
||||
*
|
||||
* @note This number versions both OpenThread platform and user APIs.
|
||||
*/
|
||||
#define OPENTHREAD_API_VERSION (462)
|
||||
#define OPENTHREAD_API_VERSION (463)
|
||||
|
||||
/**
|
||||
* @addtogroup api-instance
|
||||
|
||||
@@ -176,6 +176,7 @@ typedef struct otMleCounters
|
||||
uint16_t mAttachAttempts; ///< Number of attach attempts while device was detached.
|
||||
uint16_t mPartitionIdChanges; ///< Number of changes to partition ID.
|
||||
uint16_t mBetterPartitionAttachAttempts; ///< Number of attempts to attach to a better partition.
|
||||
uint16_t mBetterParentAttachAttempts; ///< Number of attempts to attach to find a better parent (parent search).
|
||||
|
||||
/**
|
||||
* Role time tracking.
|
||||
|
||||
@@ -1069,6 +1069,7 @@ Role Leader: 1
|
||||
Attach Attempts: 1
|
||||
Partition Id Changes: 1
|
||||
Better Partition Attach Attempts: 0
|
||||
Better Parent Attach Attempts: 0
|
||||
Parent Changes: 0
|
||||
Time Disabled Milli: 10026
|
||||
Time Detached Milli: 6852
|
||||
|
||||
@@ -2308,6 +2308,7 @@ template <> otError Interpreter::Process<Cmd("counters")>(Arg aArgs[])
|
||||
* Attach Attempts: 1
|
||||
* Partition Id Changes: 1
|
||||
* Better Partition Attach Attempts: 0
|
||||
* Better Parent Attach Attempts: 0
|
||||
* Parent Changes: 0
|
||||
* Done
|
||||
* @endcode
|
||||
@@ -2334,6 +2335,7 @@ template <> otError Interpreter::Process<Cmd("counters")>(Arg aArgs[])
|
||||
{&otMleCounters::mAttachAttempts, "Attach Attempts"},
|
||||
{&otMleCounters::mPartitionIdChanges, "Partition Id Changes"},
|
||||
{&otMleCounters::mBetterPartitionAttachAttempts, "Better Partition Attach Attempts"},
|
||||
{&otMleCounters::mBetterParentAttachAttempts, "Better Parent Attach Attempts"},
|
||||
{&otMleCounters::mParentChanges, "Parent Changes"},
|
||||
};
|
||||
|
||||
|
||||
@@ -46,21 +46,46 @@
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
|
||||
*
|
||||
* Define as 1 to enable periodic parent search feature.
|
||||
* Define as 1 to enable the periodic parent search feature.
|
||||
*
|
||||
* When this feature is enabled an end-device/child (while staying attached) will periodically search for a possible
|
||||
* better parent and will switch parent if a better one is found.
|
||||
*
|
||||
* The child will periodically check the average RSS value for the current parent, and only if it is below a specific
|
||||
* threshold, a parent search is performed. The `OPENTHREAD_CONFIG_PARENT_SEARCH_CHECK_INTERVAL` specifies the
|
||||
* check interval (in seconds) and `OPENTHREAD_CONFIG_PARENT_SEARCH_RSS_THRESHOLD` gives the RSS threshold.
|
||||
* When this feature is enabled, an end device/child (while staying attached) periodically searches for a potentially
|
||||
* better parent and switches parents if a better one is found.
|
||||
*
|
||||
* Since the parent search process can be power consuming (child needs to stays in RX mode to collect parent response)
|
||||
* and to limit its impact on battery-powered devices, after a parent search is triggered, the child will not trigger
|
||||
* another one before a specified backoff interval specified by `OPENTHREAD_CONFIG_PARENT_SEARCH_BACKOFF_INTERVAL`.
|
||||
* The parent search mechanism depends on whether the device is an FTD child or an MTD child.
|
||||
*
|
||||
* FTD Child
|
||||
*
|
||||
* - An FTD child receives and processes MLE Advertisements from neighboring routers. It uses this information to track
|
||||
* the one-way link quality to each, which is later used to compare and select potential new parents.
|
||||
* - Every `OPENTHREAD_CONFIG_PARENT_SEARCH_CHECK_INTERVAL` seconds, an FTD child tries to select a better parent.
|
||||
* The FTD child checks the list of neighboring routers and the tracked link quality information. A new parent is
|
||||
* selected only if its average RSS exceeds the current parent's RSS by a margin specified by the configuration
|
||||
* `OPENTHREAD_CONFIG_PARENT_SEARCH_RSS_MARGIN` configuration.
|
||||
* - If the attach attempt to the selected router fails (e.g., the router already has the maximum number of children it
|
||||
* can support), the FTD child ensures that the same router cannot be selected again until a "reselect timeout"
|
||||
* expires. This avoids repeated attempts to the same router. This timeout is specified by the configuration
|
||||
* `OPENTHREAD_CONFIG_PARENT_SEARCH_RESELECT_TIMEOUT`.
|
||||
*
|
||||
* MTD Child
|
||||
*
|
||||
* - Every `OPENTHREAD_CONFIG_PARENT_SEARCH_CHECK_INTERVAL` seconds, an MTD child checks its average RSS to its
|
||||
* current parent. The child starts a parent search process only if the average RSS is below
|
||||
* `OPENTHREAD_CONFIG_PARENT_SEARCH_RSS_THRESHOLD`.
|
||||
* - This ensures that an MTD child already attached to a parent with good link quality does not waste energy
|
||||
* searching for better parents.
|
||||
* - The MTD child sends an MLE Parent Request to discover possible new parents. Because this process can be
|
||||
* power-consuming (the child needs to stay in RX mode to collect parent responses), and to limit its impact on
|
||||
* battery-powered devices, after a parent search is triggered on an MTD, the MTD child does not trigger another
|
||||
* one before the specified backoff interval (`OPENTHREAD_CONFIG_PARENT_SEARCH_BACKOFF_INTERVAL`) expires.
|
||||
*
|
||||
* This feature is enabled by default on FTD builds. It is recommended that it also be enabled on MTD builds. This may
|
||||
* require the platform integrator (device vendor) to select appropriate configuration values for this feature,
|
||||
* particularly `OPENTHREAD_CONFIG_PARENT_SEARCH_BACKOFF_INTERVAL`, which can impact how often a (battery-powered)
|
||||
* sleepy child may search for a parent, taking into account its impact on the device's battery life.
|
||||
*/
|
||||
#ifndef OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
|
||||
#define OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE 0
|
||||
#define OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE OPENTHREAD_FTD
|
||||
#endif
|
||||
|
||||
/**
|
||||
@@ -77,7 +102,8 @@
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_PARENT_SEARCH_BACKOFF_INTERVAL
|
||||
*
|
||||
* Specifies the backoff interval in seconds for a child to not perform a parent search after triggering one.
|
||||
* Specifies the backoff interval in seconds for a child to not perform a parent search after triggering one. This is
|
||||
* used when device is an MTD child.
|
||||
*
|
||||
* Applicable only if periodic parent search feature is enabled (see `OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE`).
|
||||
*/
|
||||
@@ -97,6 +123,31 @@
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_PARENT_SEARCH_RESELECT_TIMEOUT
|
||||
*
|
||||
* Specifies parent reselect timeout duration in seconds used on FTD child devices. When an attach attempt to a
|
||||
* neighboring router selected as a potential new parent fails, the same router cannot be selected again until this
|
||||
* timeout expires.
|
||||
*
|
||||
* Applicable only if periodic parent search feature is enabled (see `OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE`).
|
||||
*
|
||||
*/
|
||||
#ifndef OPENTHREAD_CONFIG_PARENT_SEARCH_RESELECT_TIMEOUT
|
||||
#define OPENTHREAD_CONFIG_PARENT_SEARCH_RESELECT_TIMEOUT (90 * 60)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_PARENT_SEARCH_RSS_MARGIN
|
||||
*
|
||||
* Specifies the RSS margin over the current parent RSS for allowing selection of a neighboring router as a potential
|
||||
* new parent to attach to. Used on FTD child devices.
|
||||
*
|
||||
*/
|
||||
#ifndef OPENTHREAD_CONFIG_PARENT_SEARCH_RSS_MARGIN
|
||||
#define OPENTHREAD_CONFIG_PARENT_SEARCH_RSS_MARGIN 7
|
||||
#endif
|
||||
|
||||
/*
|
||||
* @}
|
||||
*/
|
||||
|
||||
|
||||
+164
-20
@@ -126,7 +126,7 @@ Error Mle::Enable(void)
|
||||
SuccessOrExit(error = mSocket.Bind(kUdpPort));
|
||||
|
||||
#if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
|
||||
mParentSearch.StartTimer();
|
||||
mParentSearch.SetEnabled(true);
|
||||
#endif
|
||||
exit:
|
||||
return error;
|
||||
@@ -1310,6 +1310,13 @@ Error Mle::DetermineParentRequestType(ParentRequestType &aType) const
|
||||
|
||||
OT_ASSERT(mAttachState == kAttachStateParentRequest);
|
||||
|
||||
if (mAttachMode == kSelectedParent)
|
||||
{
|
||||
aType = kToSelectedRouter;
|
||||
VerifyOrExit(mParentRequestCounter <= 1, error = kErrorNotFound);
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
aType = kToRoutersAndReeds;
|
||||
|
||||
// If device is not yet attached, `mAttachCounter` will track the
|
||||
@@ -1378,14 +1385,23 @@ bool Mle::HasAcceptableParentCandidate(void) const
|
||||
|
||||
if (IsChild())
|
||||
{
|
||||
// If already attached, accept the parent candidate if
|
||||
// we are trying to attach to a better partition or if a
|
||||
// Parent Response was also received from the current parent
|
||||
// to which the device is attached. This ensures that the
|
||||
// new parent candidate is compared with the current parent
|
||||
// and that it is indeed preferred over the current one.
|
||||
switch (mAttachMode)
|
||||
{
|
||||
case kBetterPartition:
|
||||
break;
|
||||
|
||||
VerifyOrExit(mReceivedResponseFromParent || (mAttachMode == kBetterPartition));
|
||||
case kAnyPartition:
|
||||
case kSamePartition:
|
||||
case kDowngradeToReed:
|
||||
case kBetterParent:
|
||||
case kSelectedParent:
|
||||
// Ensure that a Parent Response was received from the
|
||||
// current parent to which the device is attached, so
|
||||
// that the new parent candidate can be compared with the
|
||||
// current parent and confirmed to be preferred.
|
||||
VerifyOrExit(mReceivedResponseFromParent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
hasAcceptableParent = true;
|
||||
@@ -1451,7 +1467,18 @@ void Mle::HandleAttachTimer(void)
|
||||
if (DetermineParentRequestType(type) == kErrorNone)
|
||||
{
|
||||
SendParentRequest(type);
|
||||
delay = (type == kToRouters) ? kParentRequestRouterTimeout : kParentRequestReedTimeout;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case kToRouters:
|
||||
case kToSelectedRouter:
|
||||
delay = kParentRequestRouterTimeout;
|
||||
break;
|
||||
case kToRoutersAndReeds:
|
||||
delay = kParentRequestReedTimeout;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1552,6 +1579,7 @@ uint32_t Mle::Reattach(void)
|
||||
{
|
||||
case kAnyPartition:
|
||||
case kBetterParent:
|
||||
case kSelectedParent:
|
||||
if (!IsChild())
|
||||
{
|
||||
if (mAlternatePanId != Mac::kPanIdBroadcast)
|
||||
@@ -1606,6 +1634,7 @@ void Mle::SendParentRequest(ParentRequestType aType)
|
||||
switch (aType)
|
||||
{
|
||||
case kToRouters:
|
||||
case kToSelectedRouter:
|
||||
scanMask = ScanMaskTlv::kRouterFlag;
|
||||
break;
|
||||
|
||||
@@ -1623,12 +1652,38 @@ void Mle::SendParentRequest(ParentRequestType aType)
|
||||
SuccessOrExit(error = message->AppendTimeRequestTlv());
|
||||
#endif
|
||||
|
||||
destination.SetToLinkLocalAllRoutersMulticast();
|
||||
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
|
||||
if (aType == kToSelectedRouter)
|
||||
{
|
||||
TxMessage *messageToCurParent = static_cast<TxMessage *>(message->Clone());
|
||||
|
||||
VerifyOrExit(messageToCurParent != nullptr, error = kErrorNoBufs);
|
||||
|
||||
destination.SetToLinkLocalAddress(mParent.GetExtAddress());
|
||||
error = messageToCurParent->SendTo(destination);
|
||||
|
||||
if (error != kErrorNone)
|
||||
{
|
||||
messageToCurParent->Free();
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
Log(kMessageSend, kTypeParentRequestToRouters, destination);
|
||||
|
||||
destination.SetToLinkLocalAddress(mParentSearch.GetSelectedParent().GetExtAddress());
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
destination.SetToLinkLocalAllRoutersMulticast();
|
||||
}
|
||||
|
||||
SuccessOrExit(error = message->SendTo(destination));
|
||||
|
||||
switch (aType)
|
||||
{
|
||||
case kToRouters:
|
||||
case kToSelectedRouter:
|
||||
Log(kMessageSend, kTypeParentRequestToRouters, destination);
|
||||
break;
|
||||
|
||||
@@ -3034,7 +3089,6 @@ void Mle::HandleParentResponse(RxInfo &aRxInfo)
|
||||
switch (mAttachMode)
|
||||
{
|
||||
case kAnyPartition:
|
||||
case kBetterParent:
|
||||
VerifyOrExit(!isPartitionIdSame || isIdSequenceGreater);
|
||||
break;
|
||||
|
||||
@@ -3052,6 +3106,10 @@ void Mle::HandleParentResponse(RxInfo &aRxInfo)
|
||||
VerifyOrExit(MleRouter::ComparePartitions(connectivityTlv.IsSingleton(), leaderData,
|
||||
Get<MleRouter>().IsSingleton(), mLeaderData) > 0);
|
||||
break;
|
||||
|
||||
case kBetterParent:
|
||||
case kSelectedParent:
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -3794,9 +3852,20 @@ exit:
|
||||
#endif // OPENTHREAD_CONFIG_MLE_INFORM_PREVIOUS_PARENT_ON_REATTACH
|
||||
|
||||
#if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
|
||||
|
||||
void Mle::ParentSearch::SetEnabled(bool aEnabled)
|
||||
{
|
||||
VerifyOrExit(mEnabled != aEnabled);
|
||||
mEnabled = aEnabled;
|
||||
StartTimer();
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
|
||||
void Mle::ParentSearch::HandleTimer(void)
|
||||
{
|
||||
int8_t parentRss;
|
||||
AttachMode attachMode;
|
||||
|
||||
LogInfo("PeriodicParentSearch: %s interval passed", mIsInBackoff ? "Backoff" : "Check");
|
||||
|
||||
@@ -3817,25 +3886,95 @@ void Mle::ParentSearch::HandleTimer(void)
|
||||
|
||||
VerifyOrExit(Get<Mle>().IsChild());
|
||||
|
||||
parentRss = Get<Mle>().GetParent().GetLinkInfo().GetAverageRss();
|
||||
LogInfo("PeriodicParentSearch: Parent RSS %d", parentRss);
|
||||
VerifyOrExit(parentRss != Radio::kInvalidRssi);
|
||||
|
||||
if (parentRss < kRssThreshold)
|
||||
#if OPENTHREAD_FTD
|
||||
if (Get<Mle>().IsFullThreadDevice())
|
||||
{
|
||||
LogInfo("PeriodicParentSearch: Parent RSS less than %d, searching for new parents", kRssThreshold);
|
||||
mIsInBackoff = true;
|
||||
Get<Mle>().Attach(kBetterParent);
|
||||
SuccessOrExit(SelectBetterParent());
|
||||
attachMode = kSelectedParent;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
int8_t parentRss;
|
||||
|
||||
parentRss = Get<Mle>().GetParent().GetLinkInfo().GetAverageRss();
|
||||
LogInfo("PeriodicParentSearch: Parent RSS %d", parentRss);
|
||||
VerifyOrExit(parentRss != Radio::kInvalidRssi);
|
||||
|
||||
VerifyOrExit(parentRss < kRssThreshold);
|
||||
LogInfo("PeriodicParentSearch: Parent RSS less than %d, searching for new parents", kRssThreshold);
|
||||
|
||||
mIsInBackoff = true;
|
||||
attachMode = kBetterParent;
|
||||
}
|
||||
|
||||
Get<Mle>().mCounters.mBetterParentAttachAttempts++;
|
||||
Get<Mle>().Attach(attachMode);
|
||||
|
||||
exit:
|
||||
StartTimer();
|
||||
}
|
||||
|
||||
#if OPENTHREAD_FTD
|
||||
Error Mle::ParentSearch::SelectBetterParent(void)
|
||||
{
|
||||
Error error = kErrorNone;
|
||||
|
||||
mSelectedParent = nullptr;
|
||||
|
||||
for (Router &router : Get<RouterTable>())
|
||||
{
|
||||
CompareAndUpdateSelectedParent(router);
|
||||
}
|
||||
|
||||
VerifyOrExit(mSelectedParent != nullptr, error = kErrorNotFound);
|
||||
mSelectedParent->SetParentReselectTimeout(kParentReselectTimeout);
|
||||
|
||||
LogInfo("PeriodicParentSearch: Selected router 0x%04x as parent with RSS %d", mSelectedParent->GetRloc16(),
|
||||
mSelectedParent->GetLinkInfo().GetAverageRss());
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
void Mle::ParentSearch::CompareAndUpdateSelectedParent(Router &aRouter)
|
||||
{
|
||||
int8_t routerRss;
|
||||
|
||||
VerifyOrExit(aRouter.IsSelectableAsParent());
|
||||
VerifyOrExit(aRouter.GetParentReselectTimeout() == 0);
|
||||
VerifyOrExit(aRouter.GetRloc16() != Get<Mle>().GetParent().GetRloc16());
|
||||
|
||||
routerRss = aRouter.GetLinkInfo().GetAverageRss();
|
||||
VerifyOrExit(routerRss != Radio::kInvalidRssi);
|
||||
|
||||
if (mSelectedParent == nullptr)
|
||||
{
|
||||
VerifyOrExit(routerRss >= Get<Mle>().GetParent().GetLinkInfo().GetAverageRss() + kRssMarginOverParent);
|
||||
}
|
||||
else
|
||||
{
|
||||
VerifyOrExit(routerRss > mSelectedParent->GetLinkInfo().GetAverageRss());
|
||||
}
|
||||
|
||||
mSelectedParent = &aRouter;
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
|
||||
#endif // OPENTHREAD_FTD
|
||||
|
||||
void Mle::ParentSearch::StartTimer(void)
|
||||
{
|
||||
uint32_t interval;
|
||||
|
||||
if (!mEnabled)
|
||||
{
|
||||
mTimer.Stop();
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
interval = Random::NonCrypto::GetUint32InRange(0, kJitterInterval);
|
||||
|
||||
if (mIsInBackoff)
|
||||
@@ -3850,6 +3989,9 @@ void Mle::ParentSearch::StartTimer(void)
|
||||
mTimer.Start(interval);
|
||||
|
||||
LogInfo("PeriodicParentSearch: (Re)starting timer for %s interval", mIsInBackoff ? "backoff" : "check");
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
|
||||
void Mle::ParentSearch::UpdateState(void)
|
||||
@@ -4102,6 +4244,7 @@ const char *Mle::AttachModeToString(AttachMode aMode)
|
||||
"BetterPartition", // (2) kBetterPartition
|
||||
"DowngradeToReed", // (3) kDowngradeToReed
|
||||
"BetterParent", // (4) kBetterParent
|
||||
"SelectedParent", // (5) kSelectedParent
|
||||
};
|
||||
|
||||
struct EnumCheck
|
||||
@@ -4112,6 +4255,7 @@ const char *Mle::AttachModeToString(AttachMode aMode)
|
||||
ValidateNextEnum(kBetterPartition);
|
||||
ValidateNextEnum(kDowngradeToReed);
|
||||
ValidateNextEnum(kBetterParent);
|
||||
ValidateNextEnum(kSelectedParent);
|
||||
};
|
||||
|
||||
return kAttachModeStrings[aMode];
|
||||
|
||||
+21
-1
@@ -807,6 +807,7 @@ private:
|
||||
kBetterPartition, // Attach to a better (i.e. higher weight/partition id) Thread partition.
|
||||
kDowngradeToReed, // Attach to the same Thread partition during downgrade process.
|
||||
kBetterParent, // Attach to a better parent.
|
||||
kSelectedParent, // Attach to a selected parent.
|
||||
};
|
||||
|
||||
enum AttachState : uint8_t
|
||||
@@ -857,6 +858,7 @@ private:
|
||||
{
|
||||
kToRouters, // Parent Request to routers only.
|
||||
kToRoutersAndReeds, // Parent Request to all routers and REEDs.
|
||||
kToSelectedRouter, // Parent Request to a selected router (e.g., by `ParentSearch` module).
|
||||
};
|
||||
|
||||
enum ChildUpdateRequestMode : uint8_t // Used in `SendChildUpdateRequest()`
|
||||
@@ -1215,6 +1217,7 @@ private:
|
||||
public:
|
||||
explicit ParentSearch(Instance &aInstance)
|
||||
: InstanceLocator(aInstance)
|
||||
, mEnabled(false)
|
||||
, mIsInBackoff(false)
|
||||
, mBackoffWasCanceled(false)
|
||||
, mRecentlyDetached(false)
|
||||
@@ -1223,10 +1226,14 @@ private:
|
||||
{
|
||||
}
|
||||
|
||||
void StartTimer(void);
|
||||
void SetEnabled(bool aEnabled);
|
||||
bool IsEnabled(void) const { return mEnabled; }
|
||||
void UpdateState(void);
|
||||
void SetRecentlyDetached(void) { mRecentlyDetached = true; }
|
||||
void HandleTimer(void);
|
||||
#if OPENTHREAD_FTD
|
||||
const Neighbor &GetSelectedParent(void) const { return *mSelectedParent; }
|
||||
#endif
|
||||
|
||||
private:
|
||||
// All timer intervals are converted to milliseconds.
|
||||
@@ -1235,13 +1242,26 @@ private:
|
||||
static constexpr uint32_t kJitterInterval = (15 * 1000u);
|
||||
static constexpr int8_t kRssThreshold = OPENTHREAD_CONFIG_PARENT_SEARCH_RSS_THRESHOLD;
|
||||
|
||||
#if OPENTHREAD_FTD
|
||||
static constexpr int8_t kRssMarginOverParent = OPENTHREAD_CONFIG_PARENT_SEARCH_RSS_MARGIN;
|
||||
static constexpr uint16_t kParentReselectTimeout = OPENTHREAD_CONFIG_PARENT_SEARCH_RESELECT_TIMEOUT; // in sec
|
||||
|
||||
Error SelectBetterParent(void);
|
||||
void CompareAndUpdateSelectedParent(Router &aRouter);
|
||||
#endif
|
||||
void StartTimer(void);
|
||||
|
||||
using SearchTimer = TimerMilliIn<Mle, &Mle::HandleParentSearchTimer>;
|
||||
|
||||
bool mEnabled : 1;
|
||||
bool mIsInBackoff : 1;
|
||||
bool mBackoffWasCanceled : 1;
|
||||
bool mRecentlyDetached : 1;
|
||||
TimeMilli mBackoffCancelTime;
|
||||
SearchTimer mTimer;
|
||||
#if OPENTHREAD_FTD
|
||||
Router *mSelectedParent;
|
||||
#endif
|
||||
};
|
||||
#endif // OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
|
||||
|
||||
|
||||
@@ -375,7 +375,7 @@ void MleRouter::HandleChildStart(AttachMode aMode)
|
||||
|
||||
case kAnyPartition:
|
||||
case kBetterParent:
|
||||
|
||||
case kSelectedParent:
|
||||
// If attach was initiated due to receiving an MLE Announce
|
||||
// message, all rx-on-when-idle devices will immediately
|
||||
// attempt to attach as well. This aligns with the Thread 1.1
|
||||
@@ -1311,6 +1311,10 @@ Error MleRouter::HandleAdvertisementOnFtd(RxInfo &aRxInfo, uint16_t aSourceAddre
|
||||
}
|
||||
}
|
||||
|
||||
#if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
|
||||
router->SetSelectableAsParent(true);
|
||||
#endif
|
||||
|
||||
router->SetLastHeard(TimerMilli::GetNow());
|
||||
|
||||
ExitNow();
|
||||
@@ -1664,6 +1668,15 @@ void MleRouter::HandleTimeTick(void)
|
||||
}
|
||||
}
|
||||
|
||||
#if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
|
||||
router.DecrementParentReselectTimeout();
|
||||
|
||||
if (age >= kMaxNeighborAge)
|
||||
{
|
||||
router.SetSelectableAsParent(false);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (router.IsStateLinkRequest() && (age >= kLinkRequestTimeout))
|
||||
{
|
||||
LogInfo("Router 0x%04x - Link Request timeout expired", router.GetRloc16());
|
||||
|
||||
@@ -140,6 +140,42 @@ public:
|
||||
*/
|
||||
bool SetNextHopToInvalid(void);
|
||||
|
||||
#if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
|
||||
/**
|
||||
* Indicates whether or not this router can be selected as parent.
|
||||
*
|
||||
* @retval TRUE The router is selectable as parent.
|
||||
* @retval FALSE The router is not selectable as parent.
|
||||
*/
|
||||
bool IsSelectableAsParent(void) const { return mIsSelectableAsParent; }
|
||||
|
||||
/**
|
||||
* Sets whether or not this router is selectable as parent.
|
||||
*
|
||||
* @param[in] aIsSelectable Boolean indicating whether or not router is selectable as parent.
|
||||
*/
|
||||
void SetSelectableAsParent(bool aIsSelectable) { mIsSelectableAsParent = aIsSelectable; }
|
||||
|
||||
/**
|
||||
* Sets timeout duration in seconds to block reselecting this router as parent.
|
||||
*
|
||||
* @param[in] aTimeout The timeout duration in seconds.
|
||||
*/
|
||||
void SetParentReselectTimeout(uint16_t aTimeout) { mParentReselectTimeout = aTimeout; }
|
||||
|
||||
/**
|
||||
* Gets the remaining timeout duration in seconds to block reselecting this router parent.
|
||||
*
|
||||
* @returns The remaining timeout duration in seconds.
|
||||
*/
|
||||
uint16_t GetParentReselectTimeout(void) const { return mParentReselectTimeout; }
|
||||
|
||||
/**
|
||||
* Decrements the reselect timeout duration (if non-zero).
|
||||
*/
|
||||
void DecrementParentReselectTimeout(void) { (mParentReselectTimeout > 0) ? mParentReselectTimeout-- : 0; }
|
||||
#endif
|
||||
|
||||
private:
|
||||
uint8_t mNextHop; ///< The next hop towards this router
|
||||
uint8_t mLinkQualityOut : 2; ///< The link quality out for this router
|
||||
@@ -149,6 +185,10 @@ private:
|
||||
#else
|
||||
uint8_t mCost : 4; ///< The cost to this router via neighbor router
|
||||
#endif
|
||||
#if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
|
||||
bool mIsSelectableAsParent : 1;
|
||||
uint16_t mParentReselectTimeout;
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -366,6 +366,12 @@ class Node(object):
|
||||
def get_nexthop(self, rloc16):
|
||||
return self._cli_single_output('nexthop', rloc16)
|
||||
|
||||
def get_child_max(self):
|
||||
return self._cli_single_output('childmax')
|
||||
|
||||
def set_child_max(self, childmax):
|
||||
self._cli_no_output('childmax', childmax)
|
||||
|
||||
def get_parent_info(self):
|
||||
outputs = self.cli('parent')
|
||||
result = {}
|
||||
@@ -891,6 +897,15 @@ class Node(object):
|
||||
"""Removes a given node (of node `Node) from the allowlist"""
|
||||
self._cli_no_output('macfilter addr remove', node.get_ext_addr())
|
||||
|
||||
def denylist_node(self, node):
|
||||
"""Adds a given node to the denylist of `self` and enables denylisting on `self`"""
|
||||
self._cli_no_output('macfilter addr add', node.get_ext_addr())
|
||||
self._cli_no_output('macfilter addr denylist')
|
||||
|
||||
def un_denylist_node(self, node):
|
||||
"""Removes a given node (of node `Node) from the denylist"""
|
||||
self._cli_no_output('macfilter addr remove', node.get_ext_addr())
|
||||
|
||||
def set_macfilter_lqi_to_node(self, node, lqi):
|
||||
self._cli_no_output('macfilter rss add-lqi', node.get_ext_addr(), lqi)
|
||||
|
||||
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright (c) 2024, 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.
|
||||
|
||||
from cli import verify
|
||||
from cli import verify_within
|
||||
import cli
|
||||
import time
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------------
|
||||
# Test description:
|
||||
#
|
||||
# Validate parent search on FTD child (FED/REED).
|
||||
#
|
||||
# Topology:
|
||||
#
|
||||
# The link between `r1` and `fed` is configured to be poor, ensuring
|
||||
# `fed` will search for a new parent. `r2` is configured with
|
||||
# `set_child_max(1)`, allowing only one child. `c2` is already
|
||||
# attached to `r2`, preventing `r2` from accepting `fed` as a child.
|
||||
# Later, `r3` is added to the network, and the test validates that
|
||||
# `fed` successfully switches to `r3` as its parent.
|
||||
#
|
||||
#
|
||||
# r1 ---- r2 r3 -- r1 -- r2
|
||||
# . | \ |
|
||||
# . | ==> \ |
|
||||
# fed c2 fed c2
|
||||
|
||||
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
|
||||
print('-' * 120)
|
||||
print('Starting \'{}\''.format(test_name))
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------------
|
||||
# Creating `cli.Node` instances
|
||||
|
||||
speedup = 40
|
||||
cli.Node.set_time_speedup_factor(speedup)
|
||||
|
||||
fed = cli.Node()
|
||||
r1 = cli.Node()
|
||||
r2 = cli.Node()
|
||||
c2 = cli.Node()
|
||||
r3 = cli.Node()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------------
|
||||
# Test Implementation
|
||||
|
||||
r1.set_macfilter_lqi_to_node(fed, 1)
|
||||
fed.set_macfilter_lqi_to_node(r1, 1)
|
||||
|
||||
r1.allowlist_node(fed)
|
||||
r1.allowlist_node(r2)
|
||||
r1.allowlist_node(r3)
|
||||
|
||||
r2.allowlist_node(fed)
|
||||
r2.allowlist_node(r1)
|
||||
r2.allowlist_node(c2)
|
||||
r2.allowlist_node(r3)
|
||||
|
||||
c2.allowlist_node(r2)
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# `r2` is allowed to have one child only (`c2`)
|
||||
|
||||
r2.set_child_max(1)
|
||||
verify(int(r2.get_child_max()) == 1)
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# Form the network.
|
||||
|
||||
r1.form('fed-prnt-srch')
|
||||
fed.join(r1, cli.JOIN_TYPE_REED)
|
||||
r2.join(r1)
|
||||
c2.join(r2, cli.JOIN_TYPE_END_DEVICE)
|
||||
|
||||
verify(r1.get_state() == 'leader')
|
||||
verify(fed.get_state() == 'child')
|
||||
verify(r2.get_state() == 'router')
|
||||
verify(c2.get_state() == 'child')
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# Check that `c2` is attached to `r2`.
|
||||
|
||||
info = c2.get_parent_info()
|
||||
verify(int(info['Rloc'], 16) == int(r2.get_rloc16(), 16))
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# Check that `fed` is attached to `r1` and has poor link quality.
|
||||
|
||||
info = fed.get_parent_info()
|
||||
verify(int(info['Rloc'], 16) == int(r1.get_rloc16(), 16))
|
||||
verify(int(info['Link Quality In']) == 1)
|
||||
verify(int(info['Link Quality Out']) == 1)
|
||||
|
||||
verify(int(cli.Node.parse_list(fed.get_mle_counter())['Better Parent Attach Attempts']) == 0)
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# The `toranj` config sets `PARENT_SEARCH_CHECK_INTERVAL` to 120 seconds.
|
||||
|
||||
time.sleep(130 / speedup)
|
||||
|
||||
# Verify that parent search mechanism did trigger an attach attempt on
|
||||
# `fed`.
|
||||
|
||||
verify(int(cli.Node.parse_list(fed.get_mle_counter())['Better Parent Attach Attempts']) == 1)
|
||||
|
||||
# Since `r2` can have one child only and already has `c2`, the attach
|
||||
# attempt should fail and `fed` should still have `r1` as its
|
||||
# parent.
|
||||
|
||||
info = fed.get_parent_info()
|
||||
verify(int(info['Rloc'], 16) == int(r1.get_rloc16(), 16))
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# Ensure that `fed` does not initiate any further attach attempts, as
|
||||
# the only available parent (`r2`) should be in the "reselect timeout"
|
||||
# period. This should prevent `fed` from attempting to attach to `r2`.
|
||||
|
||||
time.sleep(150 / speedup)
|
||||
|
||||
verify(int(cli.Node.parse_list(fed.get_mle_counter())['Better Parent Attach Attempts']) == 1)
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# Add `r3` to the network and wait for `PARENT_SEARCH_CHECK_INTERVAL`.
|
||||
# This should trigger a parent search on `fed`. Validate that
|
||||
# `fed` successfully switches to `r3` as its new parent.
|
||||
|
||||
r3.join(r1)
|
||||
verify(r3.get_state() == 'router')
|
||||
|
||||
time.sleep(130 / speedup)
|
||||
|
||||
counters = cli.Node.parse_list(fed.get_mle_counter())
|
||||
verify(int(counters['Better Parent Attach Attempts']) == 2)
|
||||
verify(int(counters['Parent Changes']) == 1)
|
||||
|
||||
info = fed.get_parent_info()
|
||||
verify(int(info['Rloc'], 16) == int(r3.get_rloc16(), 16))
|
||||
verify(int(info['Link Quality In']) == 3)
|
||||
verify(int(info['Link Quality Out']) == 3)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------------
|
||||
# Test finished
|
||||
|
||||
cli.Node.finalize_all_nodes()
|
||||
|
||||
print('\'{}\' passed.'.format(test_name))
|
||||
@@ -152,6 +152,10 @@
|
||||
|
||||
#define OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE 1
|
||||
|
||||
#define OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE 1
|
||||
|
||||
#define OPENTHREAD_CONFIG_PARENT_SEARCH_CHECK_INTERVAL 120
|
||||
|
||||
#define OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE 1
|
||||
|
||||
#define OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE 1
|
||||
|
||||
@@ -198,6 +198,7 @@ if [ "$TORANJ_CLI" = 1 ]; then
|
||||
run cli/test-031-service-aloc-route-lookup.py
|
||||
run cli/test-032-leader-take-over.py
|
||||
run cli/test-033-alt-short-addr-role-transition.py
|
||||
run cli/test-034-fed-parent-search.py
|
||||
run cli/test-035-context-id-change-addr-reg.py
|
||||
run cli/test-400-srp-client-server.py
|
||||
run cli/test-401-srp-server-address-cache-snoop.py
|
||||
|
||||
Reference in New Issue
Block a user