mirror of
https://github.com/espressif/openthread.git
synced 2026-06-05 21:14:49 +00:00
ecd4c92465
This commit removes the OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE feature and all associated code, tests, CLI commands, and harness references. Changes: - Removed OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE definition and all assert/preprocessor checks. - Completely deleted dua_manager.cpp and dua_manager.hpp. - Removed DUA registration notifying and request URI paths. - Cleaned up all references to Domain Unicast Address (DUA) across child management, notifier, time ticker, and MLE. - Removed DUA commands and logic from the CLI and Python cert tests (including packet verifier). - Verified that the entire codebase compiles clean and all tests successfully pass using the Nexus test suite.
3970 lines
117 KiB
C++
3970 lines
117 KiB
C++
/*
|
|
* Copyright (c) 2016, 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.
|
|
*/
|
|
|
|
/**
|
|
* @file
|
|
* This file implements MLE functionality required for the Thread Router and Leader roles.
|
|
*/
|
|
|
|
#include "mle.hpp"
|
|
|
|
#if OPENTHREAD_FTD
|
|
|
|
#include "instance/instance.hpp"
|
|
|
|
namespace ot {
|
|
namespace Mle {
|
|
|
|
RegisterLogModule("Mle");
|
|
|
|
void Mle::SetAlternateRloc16(uint16_t aRloc16)
|
|
{
|
|
VerifyOrExit(aRloc16 != Mac::kShortAddrInvalid);
|
|
|
|
LogInfo("Setting alternate RLOC16 0x%04x", aRloc16);
|
|
|
|
Get<Mac::Mac>().SetAlternateShortAddress(aRloc16);
|
|
mAlternateRloc16Timeout = kAlternateRloc16Timeout;
|
|
|
|
exit:
|
|
return;
|
|
}
|
|
|
|
void Mle::ClearAlternateRloc16(void)
|
|
{
|
|
VerifyOrExit(Get<Mac::Mac>().GetAlternateShortAddress() != Mac::kShortAddrInvalid);
|
|
|
|
LogInfo("Clearing alternate RLOC16");
|
|
Get<Mac::Mac>().SetAlternateShortAddress(Mac::kShortAddrInvalid);
|
|
|
|
exit:
|
|
mAlternateRloc16Timeout = 0;
|
|
}
|
|
|
|
void Mle::HandlePartitionChange(void)
|
|
{
|
|
mPreviousPartitionId = mLeaderData.GetPartitionId();
|
|
mPreviousPartitionRouterIdSequence = mRouterTable.GetRouterIdSequence();
|
|
mPreviousPartitionIdTimeout = GetNetworkIdTimeout();
|
|
|
|
Get<AddressResolver>().Clear();
|
|
IgnoreError(Get<Tmf::Agent>().AbortTransaction(HandleAddressSolicitResponse, this));
|
|
mRouterTable.Clear();
|
|
}
|
|
|
|
bool Mle::RoleTransitioner::DetermineIfRouterRoleAllowed(void) const
|
|
{
|
|
bool allowed = false;
|
|
const SecurityPolicy &secPolicy = Get<KeyManager>().GetSecurityPolicy();
|
|
|
|
VerifyOrExit(mRouterEligible && Get<Mle>().IsFullThreadDevice());
|
|
|
|
#if OPENTHREAD_CONFIG_THREAD_VERSION == OT_THREAD_VERSION_1_1
|
|
VerifyOrExit(secPolicy.mRoutersEnabled);
|
|
#else
|
|
|
|
if (secPolicy.mCommercialCommissioningEnabled)
|
|
{
|
|
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
|
|
VerifyOrExit(mCcmEnabled || secPolicy.mNonCcmRoutersEnabled);
|
|
#else
|
|
VerifyOrExit(secPolicy.mNonCcmRoutersEnabled);
|
|
#endif
|
|
}
|
|
|
|
if (!secPolicy.mRoutersEnabled)
|
|
{
|
|
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
|
|
VerifyOrExit(!mThreadVersionCheckEnabled ||
|
|
secPolicy.mVersionThresholdForRouting + SecurityPolicy::kVersionThresholdOffsetVersion <=
|
|
kThreadVersion);
|
|
#else
|
|
VerifyOrExit(secPolicy.mVersionThresholdForRouting + SecurityPolicy::kVersionThresholdOffsetVersion <=
|
|
kThreadVersion);
|
|
#endif
|
|
}
|
|
|
|
#endif // #if OPENTHREAD_CONFIG_THREAD_VERSION == OT_THREAD_VERSION_1_1
|
|
|
|
allowed = true;
|
|
|
|
exit:
|
|
return allowed;
|
|
}
|
|
|
|
void Mle::RoleTransitioner::UpdateRouterRoleAllowed(UpdateRouterRoleAllowedReason aReason)
|
|
{
|
|
bool allowed = DetermineIfRouterRoleAllowed();
|
|
|
|
VerifyOrExit(allowed != mRouterRoleAllowed);
|
|
mRouterRoleAllowed = allowed;
|
|
|
|
if (Get<Mle>().IsAttached())
|
|
{
|
|
Get<Mac::Mac>().SetBeaconEnabled(mRouterRoleAllowed);
|
|
}
|
|
|
|
// Take action based on the current role, the new `mRouterRoleAllowed`,
|
|
// and the reason for the change.
|
|
|
|
if (Get<Mle>().IsChild() && mRouterRoleAllowed && (aReason == kReasonConfigParameterChanged))
|
|
{
|
|
StartTimeout();
|
|
}
|
|
|
|
if (Get<Mle>().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(Get<Mle>().BecomeDetached());
|
|
break;
|
|
|
|
case kReasonSecurityPolicyChanged:
|
|
VerifyOrExit(!IsTransitionPending());
|
|
StartTimeout();
|
|
|
|
if (Get<Mle>().IsLeader())
|
|
{
|
|
IncreaseTimeout(kLeaderDowngradeExtraDelay);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
exit:
|
|
return;
|
|
}
|
|
|
|
Error Mle::RoleTransitioner::SetRouterEligible(bool aEligible)
|
|
{
|
|
Error error = kErrorNone;
|
|
|
|
if (!Get<Mle>().IsFullThreadDevice())
|
|
{
|
|
VerifyOrExit(!aEligible, error = kErrorNotCapable);
|
|
}
|
|
|
|
VerifyOrExit(aEligible != mRouterEligible);
|
|
|
|
mRouterEligible = aEligible;
|
|
UpdateRouterRoleAllowed(kReasonConfigParameterChanged);
|
|
|
|
exit:
|
|
return error;
|
|
}
|
|
|
|
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
|
|
void Mle::RoleTransitioner::SetCcmEnabled(bool aEnabled)
|
|
{
|
|
mCcmEnabled = aEnabled;
|
|
UpdateRouterRoleAllowed(kReasonConfigParameterChanged);
|
|
}
|
|
|
|
void Mle::RoleTransitioner::SetThreadVersionCheckEnabled(bool aEnabled)
|
|
{
|
|
mThreadVersionCheckEnabled = aEnabled;
|
|
UpdateRouterRoleAllowed(kReasonConfigParameterChanged);
|
|
}
|
|
#endif
|
|
|
|
#if OPENTHREAD_CONFIG_MLE_DEVICE_PROPERTY_LEADER_WEIGHT_ENABLE
|
|
void Mle::SetDeviceProperties(const DeviceProperties &aDeviceProperties)
|
|
{
|
|
mDeviceProperties = aDeviceProperties;
|
|
mDeviceProperties.ClampWeightAdjustment();
|
|
SetLeaderWeight(mDeviceProperties.CalculateLeaderWeight());
|
|
}
|
|
#endif
|
|
|
|
Error Mle::BecomeRouter(RouterUpgradeReason aReason)
|
|
{
|
|
Error error = kErrorNone;
|
|
|
|
switch (mRole)
|
|
{
|
|
case kRoleChild:
|
|
break;
|
|
|
|
case kRoleDisabled:
|
|
case kRoleDetached:
|
|
error = kErrorInvalidState;
|
|
OT_FALL_THROUGH;
|
|
|
|
case kRoleRouter:
|
|
case kRoleLeader:
|
|
ExitNow();
|
|
}
|
|
|
|
VerifyOrExit(IsRouterRoleAllowed(), error = kErrorNotCapable);
|
|
|
|
LogInfo("Attempt to become router, reason:%s", RouterUpgradeReasonToString(aReason));
|
|
|
|
Get<MeshForwarder>().SetRxOnWhenIdle(true);
|
|
mRoleTransitioner.StopTimeout();
|
|
|
|
error = SendAddressSolicit(aReason);
|
|
|
|
exit:
|
|
return error;
|
|
}
|
|
|
|
Error Mle::BecomeLeader(LeaderWeightCheck aMode)
|
|
{
|
|
Error error = kErrorNone;
|
|
Router *router;
|
|
uint8_t leaderId;
|
|
|
|
#if OPENTHREAD_CONFIG_OPERATIONAL_DATASET_AUTO_INIT
|
|
VerifyOrExit(!Get<MeshCoP::ActiveDatasetManager>().IsPartiallyComplete(), error = kErrorInvalidState);
|
|
#else
|
|
VerifyOrExit(Get<MeshCoP::ActiveDatasetManager>().IsComplete(), error = kErrorInvalidState);
|
|
#endif
|
|
VerifyOrExit(!IsDisabled(), error = kErrorInvalidState);
|
|
VerifyOrExit(!IsLeader(), error = kErrorNone);
|
|
VerifyOrExit(IsRouterRoleAllowed(), error = kErrorNotCapable);
|
|
|
|
if ((aMode == kCheckLeaderWeight) && IsAttached())
|
|
{
|
|
VerifyOrExit(mLeaderWeight > mLeaderData.GetWeighting(), error = kErrorNotCapable);
|
|
}
|
|
|
|
mRouterTable.Clear();
|
|
|
|
leaderId = SelectLeaderId();
|
|
|
|
SetLeaderData(SelectPartitionId(), mLeaderWeight, leaderId);
|
|
|
|
router = mRouterTable.Allocate(leaderId);
|
|
OT_ASSERT(router != nullptr);
|
|
|
|
SetRouterId(leaderId);
|
|
router->SetExtAddress(Get<Mac::Mac>().GetExtAddress());
|
|
|
|
Get<NetworkData::Leader>().Reset();
|
|
Get<MeshCoP::Leader>().SetEmptyCommissionerData();
|
|
|
|
SetStateLeader(Rloc16FromRouterId(leaderId), kStartingAsLeader);
|
|
|
|
exit:
|
|
return error;
|
|
}
|
|
|
|
uint8_t Mle::SelectLeaderId(void) const
|
|
{
|
|
uint8_t minId;
|
|
uint8_t maxId;
|
|
|
|
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
|
|
mRouterTable.GetRouterIdRange(minId, maxId);
|
|
#else
|
|
minId = 0;
|
|
maxId = kMaxRouterId;
|
|
#endif
|
|
|
|
return IsValueInRange(mPreviousRouterId, minId, maxId) ? mPreviousRouterId
|
|
: Random::NonCrypto::GenerateInClosedRange(minId, maxId);
|
|
}
|
|
|
|
uint32_t Mle::SelectPartitionId(void) const
|
|
{
|
|
uint32_t partitionId;
|
|
|
|
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
|
|
if (mPreferredLeaderPartitionId != 0)
|
|
{
|
|
partitionId = mPreferredLeaderPartitionId;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
partitionId = Random::NonCrypto::Generate<uint32_t>();
|
|
}
|
|
|
|
return partitionId;
|
|
}
|
|
|
|
void Mle::StopLeader(void)
|
|
{
|
|
StopAdvertiseTrickleTimer();
|
|
Get<ThreadNetif>().UnsubscribeAllRoutersMulticast();
|
|
}
|
|
|
|
void Mle::HandleDetachStart(void)
|
|
{
|
|
mRouterTable.ClearNeighbors();
|
|
StopLeader();
|
|
Get<TimeTicker>().UnregisterReceiver(TimeTicker::kMle);
|
|
}
|
|
|
|
void Mle::HandleChildStart(void)
|
|
{
|
|
mAddressSolicitRejected = false;
|
|
|
|
mRoleTransitioner.StartTimeout();
|
|
|
|
StopLeader();
|
|
Get<TimeTicker>().RegisterReceiver(TimeTicker::kMle);
|
|
|
|
Get<Mac::Mac>().SetBeaconEnabled(IsRouterRoleAllowed());
|
|
|
|
Get<ThreadNetif>().SubscribeAllRoutersMulticast();
|
|
|
|
VerifyOrExit(IsRouterIdValid(mPreviousRouterId));
|
|
|
|
switch (mAttacher.GetAttachMode())
|
|
{
|
|
case kDowngradeToReed:
|
|
SendAddressRelease();
|
|
|
|
if (HasChildren())
|
|
{
|
|
RemoveChildren();
|
|
}
|
|
|
|
SetRouterId(kInvalidRouterId);
|
|
break;
|
|
|
|
case kSamePartition:
|
|
if (HasChildren())
|
|
{
|
|
IgnoreError(BecomeRouter(kReasonHaveChildIdRequest));
|
|
}
|
|
|
|
break;
|
|
|
|
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
|
|
// specification (Section 4.8.1):
|
|
//
|
|
// "If the received value is newer and the channel and/or PAN
|
|
// ID in the Announce message differ from those currently in
|
|
// use, the receiving device attempts to attach using the
|
|
// channel and PAN ID received from the Announce message."
|
|
//
|
|
// Since parent-child relationships are unlikely to persist in
|
|
// the new partition, we remove all children here. The
|
|
// decision to become router is determined based on the new
|
|
// partition's status.
|
|
|
|
if (mAnnounceHandler.IsAnnounceAttaching() && HasChildren())
|
|
{
|
|
RemoveChildren();
|
|
}
|
|
|
|
OT_FALL_THROUGH;
|
|
|
|
case kBetterPartition:
|
|
if (HasChildren() && mPreviousPartitionIdRouter != mLeaderData.GetPartitionId())
|
|
{
|
|
IgnoreError(BecomeRouter(kReasonParentPartitionChange));
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
exit:
|
|
|
|
if (!mRoleTransitioner.IsRouterCountBelowUpgradeThreshold() &&
|
|
(!IsRouterIdValid(mPreviousRouterId) || !HasChildren()))
|
|
{
|
|
SetRouterId(kInvalidRouterId);
|
|
}
|
|
}
|
|
|
|
void Mle::SetStateRouter(uint16_t aRloc16)
|
|
{
|
|
// The `aStartMode` is ignored when used with `kRoleRouter`
|
|
SetStateRouterOrLeader(kRoleRouter, aRloc16, /* aStartMode */ kStartingAsLeader);
|
|
}
|
|
|
|
void Mle::SetStateLeader(uint16_t aRloc16, LeaderStartMode aStartMode)
|
|
{
|
|
SetStateRouterOrLeader(kRoleLeader, aRloc16, aStartMode);
|
|
}
|
|
|
|
void Mle::SetStateRouterOrLeader(DeviceRole aRole, uint16_t aRloc16, LeaderStartMode aStartMode)
|
|
{
|
|
if (aRole == kRoleLeader)
|
|
{
|
|
IgnoreError(Get<MeshCoP::ActiveDatasetManager>().Restore());
|
|
IgnoreError(Get<MeshCoP::PendingDatasetManager>().Restore());
|
|
}
|
|
|
|
SetRloc16(aRloc16);
|
|
|
|
SetRole(aRole);
|
|
|
|
mPrevRoleRestorer.Stop();
|
|
mAttacher.CancelAttachOnRoleChange();
|
|
|
|
mRetxTracker.Stop();
|
|
StopAdvertiseTrickleTimer();
|
|
ResetAdvertiseInterval();
|
|
|
|
Get<ThreadNetif>().SubscribeAllRoutersMulticast();
|
|
mPreviousPartitionIdRouter = mLeaderData.GetPartitionId();
|
|
Get<Mac::Mac>().SetBeaconEnabled(true);
|
|
Get<TimeTicker>().RegisterReceiver(TimeTicker::kMle);
|
|
|
|
// Avoid informing an old parent when attaching next as a child after becoming an active router
|
|
mPreviousParentRloc = kInvalidRloc16;
|
|
|
|
if (aRole == kRoleLeader)
|
|
{
|
|
GetLeaderAloc(mLeaderAloc.GetAddress());
|
|
Get<ThreadNetif>().AddUnicastAddress(mLeaderAloc);
|
|
Get<NetworkData::Leader>().Start(aStartMode);
|
|
Get<MeshCoP::ActiveDatasetManager>().StartLeader();
|
|
Get<MeshCoP::PendingDatasetManager>().StartLeader();
|
|
Get<AddressResolver>().Clear();
|
|
}
|
|
|
|
// Remove children that do not have a matching RLOC16
|
|
for (Child &child : Get<ChildTable>().Iterate(Child::kInStateValidOrRestoring))
|
|
{
|
|
if (RouterIdFromRloc16(child.GetRloc16()) != mRouterId)
|
|
{
|
|
RemoveNeighbor(child);
|
|
}
|
|
}
|
|
|
|
LogNote("Partition ID 0x%lx", ToUlong(mLeaderData.GetPartitionId()));
|
|
}
|
|
|
|
void Mle::HandleAdvertiseTrickleTimer(TrickleTimer &aTimer) { aTimer.Get<Mle>().HandleAdvertiseTrickleTimer(); }
|
|
|
|
void Mle::HandleAdvertiseTrickleTimer(void)
|
|
{
|
|
if (!IsRouterRoleAllowed())
|
|
{
|
|
mAdvertiseTrickleTimer.Stop();
|
|
}
|
|
else
|
|
{
|
|
SendMulticastAdvertisement();
|
|
}
|
|
}
|
|
|
|
void Mle::StopAdvertiseTrickleTimer(void) { mAdvertiseTrickleTimer.Stop(); }
|
|
|
|
uint32_t Mle::DetermineAdvertiseIntervalMax(void) const
|
|
{
|
|
uint32_t interval;
|
|
|
|
#if OPENTHREAD_CONFIG_MLE_LONG_ROUTES_ENABLE
|
|
interval = kAdvIntervalMaxLogRoutes;
|
|
#else
|
|
// Determine the interval based on the number of router neighbors
|
|
// with link quality 2 or higher.
|
|
|
|
interval = (Get<RouterTable>().GetNeighborCount(kLinkQuality2) + 1) * kAdvIntervalNeighborMultiplier;
|
|
interval = Clamp(interval, kAdvIntervalMaxLowerBound, kAdvIntervalMaxUpperBound);
|
|
#endif
|
|
|
|
return interval;
|
|
}
|
|
|
|
void Mle::HandleRouterTableEvent(RouterTable::Events aEvents)
|
|
{
|
|
// Callback from `RouterTable` when there is a change.
|
|
|
|
if (aEvents & RouterTable::kEventRouterAdded)
|
|
{
|
|
mRoleTransitioner.SetDowngradeBlocked(false);
|
|
}
|
|
|
|
if (IsRouterOrLeader() && mAdvertiseTrickleTimer.IsRunning())
|
|
{
|
|
mAdvertiseTrickleTimer.SetIntervalMax(DetermineAdvertiseIntervalMax());
|
|
}
|
|
}
|
|
|
|
void Mle::ResetAdvertiseInterval(void)
|
|
{
|
|
VerifyOrExit(IsRouterOrLeader());
|
|
|
|
if (!mAdvertiseTrickleTimer.IsRunning())
|
|
{
|
|
mAdvertiseTrickleTimer.Start(TrickleTimer::kModeTrickle, kAdvIntervalMin, DetermineAdvertiseIntervalMax());
|
|
}
|
|
|
|
mAdvertiseTrickleTimer.IndicateInconsistent();
|
|
|
|
exit:
|
|
return;
|
|
}
|
|
|
|
void Mle::SendMulticastAdvertisement(void)
|
|
{
|
|
VerifyOrExit(IsRouterRoleAllowed());
|
|
SendAdvertisement(Ip6::Address::GetLinkLocalAllNodesMulticast());
|
|
|
|
exit:
|
|
return;
|
|
}
|
|
|
|
void Mle::ScheduleUnicastAdvertisementTo(const Router &aRouter)
|
|
{
|
|
Ip6::Address destination;
|
|
|
|
destination.InitAsLinkLocalAddress(aRouter.GetExtAddress());
|
|
mDelayedSender.ScheduleAdvertisement(destination, GenerateRandomDelay(kMaxUnicastAdvertisementDelay));
|
|
}
|
|
|
|
void Mle::SendAdvertisement(const Ip6::Address &aDestination)
|
|
{
|
|
Error error = kErrorNone;
|
|
TxMessage *message = nullptr;
|
|
|
|
// Suppress MLE Advertisements when trying to attach to a better
|
|
// partition. Without this, a candidate parent might incorrectly
|
|
// interpret this advertisement (Source Address TLV containing an
|
|
// RLOC16 indicating device is acting as router) and reject the
|
|
// attaching device.
|
|
|
|
VerifyOrExit(!IsAttaching());
|
|
|
|
// Suppress MLE Advertisements when attempting to transition to
|
|
// router role. Advertisements as a REED while attaching to a new
|
|
// partition can cause existing children to detach
|
|
// unnecessarily.
|
|
|
|
VerifyOrExit(!mAddressSolicitPending);
|
|
|
|
VerifyOrExit((message = NewMleMessage(kCommandAdvertisement)) != nullptr, error = kErrorNoBufs);
|
|
SuccessOrExit(error = message->AppendSourceAddressAndLeaderDataTlvs());
|
|
|
|
switch (mRole)
|
|
{
|
|
case kRoleChild:
|
|
break;
|
|
|
|
case kRoleRouter:
|
|
case kRoleLeader:
|
|
SuccessOrExit(error = message->AppendRouteTlv());
|
|
break;
|
|
|
|
case kRoleDisabled:
|
|
case kRoleDetached:
|
|
OT_ASSERT(false);
|
|
}
|
|
|
|
SuccessOrExit(error = message->SendTo(aDestination));
|
|
|
|
Log(kMessageSend, kTypeAdvertisement, aDestination);
|
|
|
|
exit:
|
|
FreeMessageOnError(message, error);
|
|
LogSendError(kTypeAdvertisement, error);
|
|
}
|
|
|
|
void Mle::SendLinkRequest(Router *aRouter)
|
|
{
|
|
static const uint8_t kDetachedTlvs[] = {Tlv::kAddress16, Tlv::kRoute};
|
|
static const uint8_t kRouterTlvs[] = {Tlv::kLinkMargin};
|
|
static const uint8_t kValidNeighborTlvs[] = {Tlv::kLinkMargin, Tlv::kRoute};
|
|
|
|
Error error = kErrorNone;
|
|
TxMessage *message = nullptr;
|
|
Ip6::Address destination;
|
|
|
|
destination.Clear();
|
|
|
|
VerifyOrExit((message = NewMleMessage(kCommandLinkRequest)) != nullptr, error = kErrorNoBufs);
|
|
SuccessOrExit(error = message->AppendVersionTlv());
|
|
|
|
switch (mRole)
|
|
{
|
|
case kRoleDetached:
|
|
SuccessOrExit(error = message->AppendTlvRequestTlv(kDetachedTlvs));
|
|
break;
|
|
|
|
case kRoleChild:
|
|
SuccessOrExit(error = message->AppendSourceAddressAndLeaderDataTlvs());
|
|
break;
|
|
|
|
case kRoleRouter:
|
|
case kRoleLeader:
|
|
if (aRouter == nullptr || !aRouter->IsStateValid())
|
|
{
|
|
SuccessOrExit(error = message->AppendTlvRequestTlv(kRouterTlvs));
|
|
}
|
|
else
|
|
{
|
|
SuccessOrExit(error = message->AppendTlvRequestTlv(kValidNeighborTlvs));
|
|
}
|
|
|
|
SuccessOrExit(error = message->AppendSourceAddressAndLeaderDataTlvs());
|
|
break;
|
|
|
|
case kRoleDisabled:
|
|
OT_ASSERT(false);
|
|
}
|
|
|
|
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
|
|
SuccessOrExit(error = message->AppendTimeRequestTlv());
|
|
#endif
|
|
|
|
if (aRouter == nullptr)
|
|
{
|
|
mPrevRoleRestorer.GenerateRandomChallenge();
|
|
SuccessOrExit(error = message->AppendChallengeTlv(mPrevRoleRestorer.GetChallenge()));
|
|
destination = Ip6::Address::GetLinkLocalAllRoutersMulticast();
|
|
}
|
|
else
|
|
{
|
|
if (!aRouter->IsStateValid())
|
|
{
|
|
aRouter->GenerateChallenge();
|
|
SuccessOrExit(error = message->AppendChallengeTlv(aRouter->GetChallenge()));
|
|
}
|
|
else
|
|
{
|
|
TxChallenge challenge;
|
|
|
|
challenge.GenerateRandom();
|
|
SuccessOrExit(error = message->AppendChallengeTlv(challenge));
|
|
}
|
|
|
|
destination.InitAsLinkLocalAddress(aRouter->GetExtAddress());
|
|
aRouter->RestartLinkAcceptTimeout();
|
|
}
|
|
|
|
SuccessOrExit(error = message->SendTo(destination));
|
|
|
|
Log(kMessageSend, kTypeLinkRequest, destination);
|
|
|
|
exit:
|
|
FreeMessageOnError(message, error);
|
|
}
|
|
|
|
void Mle::HandleLinkRequest(RxInfo &aRxInfo)
|
|
{
|
|
Error error = kErrorNone;
|
|
Neighbor *neighbor = nullptr;
|
|
uint16_t version;
|
|
LeaderData leaderData;
|
|
LinkAcceptInfo info;
|
|
|
|
Log(kMessageReceive, kTypeLinkRequest, aRxInfo.mMessageInfo.GetPeerAddr());
|
|
|
|
VerifyOrExit(IsRouterOrLeader(), error = kErrorInvalidState);
|
|
|
|
VerifyOrExit(!IsAttaching(), error = kErrorInvalidState);
|
|
|
|
SuccessOrExit(error = aRxInfo.mMessage.ReadChallengeTlv(info.mRxChallenge));
|
|
|
|
SuccessOrExit(error = aRxInfo.mMessage.ReadVersionTlv(version));
|
|
|
|
switch (aRxInfo.mMessage.ReadLeaderDataTlv(leaderData))
|
|
{
|
|
case kErrorNone:
|
|
VerifyOrExit(leaderData.GetPartitionId() == mLeaderData.GetPartitionId(), error = kErrorInvalidState);
|
|
break;
|
|
case kErrorNotFound:
|
|
break;
|
|
default:
|
|
ExitNow(error = kErrorParse);
|
|
}
|
|
|
|
info.mExtAddress.SetFromIid(aRxInfo.mMessageInfo.GetPeerAddr().GetIid());
|
|
|
|
info.mLinkMargin = Get<Mac::Mac>().ComputeLinkMargin(aRxInfo.mMessage.GetAverageRss());
|
|
|
|
switch (Tlv::Find<SourceAddressTlv>(aRxInfo.mMessage, info.mRloc16))
|
|
{
|
|
case kErrorNone:
|
|
if (IsRouterRloc16(info.mRloc16))
|
|
{
|
|
Router *router = mRouterTable.FindRouterByRloc16(info.mRloc16);
|
|
|
|
VerifyOrExit(router != nullptr, error = kErrorParse);
|
|
|
|
if (!router->IsStateValid())
|
|
{
|
|
InitNeighbor(*router, aRxInfo);
|
|
router->SetState(Neighbor::kStateLinkRequest);
|
|
router->ClearLinkAcceptTimeout();
|
|
}
|
|
else
|
|
{
|
|
VerifyOrExit(router->GetExtAddress() == info.mExtAddress);
|
|
}
|
|
|
|
neighbor = router;
|
|
}
|
|
|
|
break;
|
|
|
|
case kErrorNotFound:
|
|
// A missing source address indicates that the router was
|
|
// recently reset.
|
|
VerifyOrExit(aRxInfo.IsNeighborStateValid(), error = kErrorDrop);
|
|
info.mRloc16 = aRxInfo.mNeighbor->GetRloc16();
|
|
VerifyOrExit(IsRouterRloc16(info.mRloc16), error = kErrorDrop);
|
|
neighbor = aRxInfo.mNeighbor;
|
|
break;
|
|
|
|
default:
|
|
ExitNow(error = kErrorParse);
|
|
}
|
|
|
|
switch (aRxInfo.mMessage.ReadTlvRequestTlv(info.mRequestedTlvList))
|
|
{
|
|
case kErrorNone:
|
|
case kErrorNotFound:
|
|
break;
|
|
default:
|
|
ExitNow(error = kErrorParse);
|
|
}
|
|
|
|
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
|
|
if (neighbor != nullptr)
|
|
{
|
|
neighbor->SetTimeSyncEnabled(Tlv::Find<TimeRequestTlv>(aRxInfo.mMessage, nullptr, 0) == kErrorNone);
|
|
}
|
|
#endif
|
|
|
|
#if OPENTHREAD_CONFIG_MULTI_RADIO
|
|
if (neighbor != nullptr)
|
|
{
|
|
neighbor->ClearLastRxFragmentTag();
|
|
}
|
|
#endif
|
|
|
|
aRxInfo.mClass = RxInfo::kPeerMessage;
|
|
ProcessKeySequence(aRxInfo);
|
|
|
|
if (aRxInfo.mMessageInfo.GetSockAddr().IsMulticast())
|
|
{
|
|
mDelayedSender.ScheduleLinkAccept(info, GenerateRandomDelay(kMaxLinkAcceptDelay));
|
|
}
|
|
else
|
|
{
|
|
error = SendLinkAccept(info);
|
|
}
|
|
|
|
exit:
|
|
LogProcessError(kTypeLinkRequest, error);
|
|
OT_UNUSED_VARIABLE(neighbor);
|
|
}
|
|
|
|
Error Mle::SendLinkAccept(const LinkAcceptInfo &aInfo)
|
|
{
|
|
static const uint8_t kRouterTlvs[] = {Tlv::kLinkMargin};
|
|
|
|
Error error = kErrorNone;
|
|
TxMessage *message = nullptr;
|
|
Command command = kCommandLinkAccept;
|
|
Router *router;
|
|
Ip6::Address destination;
|
|
|
|
VerifyOrExit(IsAttached());
|
|
|
|
router = mRouterTable.FindRouter(aInfo.mExtAddress);
|
|
|
|
if (router != nullptr)
|
|
{
|
|
if (router->IsStateLinkRequest())
|
|
{
|
|
command = kCommandLinkAcceptAndRequest;
|
|
router->SetLastHeard(TimerMilli::GetNow());
|
|
}
|
|
else
|
|
{
|
|
VerifyOrExit(router->IsStateValid());
|
|
}
|
|
}
|
|
|
|
VerifyOrExit((message = NewMleMessage(command)) != nullptr, error = kErrorNoBufs);
|
|
SuccessOrExit(error = message->AppendVersionTlv());
|
|
SuccessOrExit(error = message->AppendSourceAddressTlv());
|
|
SuccessOrExit(error = message->AppendResponseTlv(aInfo.mRxChallenge));
|
|
SuccessOrExit(error = message->AppendLinkAndMleFrameCounterTlvs());
|
|
|
|
SuccessOrExit(error = message->AppendLinkMarginTlv(aInfo.mLinkMargin));
|
|
|
|
if (router != nullptr)
|
|
{
|
|
SuccessOrExit(error = message->AppendLeaderDataTlv());
|
|
}
|
|
|
|
for (uint8_t tlvType : aInfo.mRequestedTlvList)
|
|
{
|
|
switch (tlvType)
|
|
{
|
|
case Tlv::kRoute:
|
|
SuccessOrExit(error = message->AppendCompactRouteTlv(aInfo.mRloc16));
|
|
break;
|
|
|
|
case Tlv::kAddress16:
|
|
VerifyOrExit(router != nullptr, error = kErrorDrop);
|
|
SuccessOrExit(error = message->AppendAddress16Tlv(router->GetRloc16()));
|
|
break;
|
|
|
|
case Tlv::kLinkMargin:
|
|
break;
|
|
|
|
default:
|
|
ExitNow(error = kErrorDrop);
|
|
}
|
|
}
|
|
|
|
if (command == kCommandLinkAcceptAndRequest)
|
|
{
|
|
router->GenerateChallenge();
|
|
|
|
SuccessOrExit(error = message->AppendChallengeTlv(router->GetChallenge()));
|
|
SuccessOrExit(error = message->AppendTlvRequestTlv(kRouterTlvs));
|
|
}
|
|
|
|
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
|
|
if (router != nullptr && router->IsTimeSyncEnabled())
|
|
{
|
|
message->SetTimeSync(true);
|
|
}
|
|
#endif
|
|
|
|
destination.InitAsLinkLocalAddress(aInfo.mExtAddress);
|
|
|
|
SuccessOrExit(error = message->SendTo(destination));
|
|
|
|
Log(kMessageSend, (command == kCommandLinkAccept) ? kTypeLinkAccept : kTypeLinkAcceptAndRequest, destination);
|
|
|
|
exit:
|
|
FreeMessageOnError(message, error);
|
|
return error;
|
|
}
|
|
|
|
void Mle::HandleLinkAccept(RxInfo &aRxInfo) { HandleLinkAcceptVariant(aRxInfo, kTypeLinkAccept); }
|
|
|
|
void Mle::HandleLinkAcceptAndRequest(RxInfo &aRxInfo) { HandleLinkAcceptVariant(aRxInfo, kTypeLinkAcceptAndRequest); }
|
|
|
|
void Mle::HandleLinkAcceptVariant(RxInfo &aRxInfo, MessageType aMessageType)
|
|
{
|
|
// Handles "Link Accept" or "Link Accept And Request".
|
|
|
|
Error error = kErrorNone;
|
|
Router *router;
|
|
Neighbor::State neighborState;
|
|
uint16_t version;
|
|
RxChallenge response;
|
|
uint16_t sourceAddress;
|
|
uint32_t linkFrameCounter;
|
|
uint32_t mleFrameCounter;
|
|
uint8_t routerId;
|
|
uint16_t address16;
|
|
RouteTlv::Data routeTlvData;
|
|
LeaderData leaderData;
|
|
uint8_t linkMargin;
|
|
bool shouldUpdateRoutes = false;
|
|
|
|
SuccessOrExit(error = Tlv::Find<SourceAddressTlv>(aRxInfo.mMessage, sourceAddress));
|
|
|
|
Log(kMessageReceive, aMessageType, aRxInfo.mMessageInfo.GetPeerAddr(), sourceAddress);
|
|
|
|
VerifyOrExit(IsRouterRloc16(sourceAddress), error = kErrorParse);
|
|
|
|
routerId = RouterIdFromRloc16(sourceAddress);
|
|
router = mRouterTable.FindRouterById(routerId);
|
|
neighborState = (router != nullptr) ? router->GetState() : Neighbor::kStateInvalid;
|
|
|
|
SuccessOrExit(error = aRxInfo.mMessage.ReadResponseTlv(response));
|
|
|
|
switch (neighborState)
|
|
{
|
|
case Neighbor::kStateLinkRequest:
|
|
VerifyOrExit(response == router->GetChallenge(), error = kErrorSecurity);
|
|
break;
|
|
|
|
case Neighbor::kStateInvalid:
|
|
VerifyOrExit(mPrevRoleRestorer.IsRestoringRouterOrLeaderRole(), error = kErrorSecurity);
|
|
VerifyOrExit(response == mPrevRoleRestorer.GetChallenge(), error = kErrorSecurity);
|
|
break;
|
|
|
|
case Neighbor::kStateValid:
|
|
break;
|
|
|
|
default:
|
|
ExitNow(error = kErrorSecurity);
|
|
}
|
|
|
|
// Remove stale neighbors
|
|
if (aRxInfo.mNeighbor && aRxInfo.mNeighbor->GetRloc16() != sourceAddress)
|
|
{
|
|
RemoveNeighbor(*aRxInfo.mNeighbor);
|
|
}
|
|
|
|
SuccessOrExit(error = aRxInfo.mMessage.ReadVersionTlv(version));
|
|
|
|
SuccessOrExit(error = aRxInfo.mMessage.ReadFrameCounterTlvs(linkFrameCounter, mleFrameCounter));
|
|
|
|
switch (Tlv::Find<LinkMarginTlv>(aRxInfo.mMessage, linkMargin))
|
|
{
|
|
case kErrorNone:
|
|
break;
|
|
case kErrorNotFound:
|
|
// The Link Margin TLV may be omitted after a reset. We wait
|
|
// for MLE Advertisements to establish the routing cost to
|
|
// the neighbor
|
|
VerifyOrExit(IsDetached(), error = kErrorNotFound);
|
|
linkMargin = 0;
|
|
break;
|
|
default:
|
|
ExitNow(error = kErrorParse);
|
|
}
|
|
|
|
switch (mRole)
|
|
{
|
|
case kRoleDetached:
|
|
SuccessOrExit(error = Tlv::Find<Address16Tlv>(aRxInfo.mMessage, address16));
|
|
VerifyOrExit(GetRloc16() == address16, error = kErrorDrop);
|
|
|
|
SuccessOrExit(error = aRxInfo.mMessage.ReadLeaderDataTlv(leaderData));
|
|
SetLeaderData(leaderData);
|
|
|
|
mRouterTable.Clear();
|
|
SuccessOrExit(error = aRxInfo.mMessage.ReadRouteTlv(routeTlvData));
|
|
SuccessOrExit(error = ProcessRouteTlv(routeTlvData, aRxInfo));
|
|
router = mRouterTable.FindRouterById(routerId);
|
|
VerifyOrExit(router != nullptr);
|
|
|
|
if (GetLeaderRloc16() == GetRloc16())
|
|
{
|
|
SetStateLeader(GetRloc16(), kRestoringLeaderRoleAfterReset);
|
|
}
|
|
else
|
|
{
|
|
SetStateRouter(GetRloc16());
|
|
}
|
|
|
|
mRetrieveNewNetworkData = true;
|
|
IgnoreError(SendDataRequest(aRxInfo.mMessageInfo.GetPeerAddr()));
|
|
shouldUpdateRoutes = true;
|
|
|
|
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
|
|
Get<TimeSync>().HandleTimeSyncMessage(aRxInfo.mMessage);
|
|
#endif
|
|
break;
|
|
|
|
case kRoleChild:
|
|
VerifyOrExit(router != nullptr);
|
|
break;
|
|
|
|
case kRoleRouter:
|
|
case kRoleLeader:
|
|
VerifyOrExit(router != nullptr);
|
|
|
|
SuccessOrExit(error = aRxInfo.mMessage.ReadLeaderDataTlv(leaderData));
|
|
VerifyOrExit(leaderData.GetPartitionId() == mLeaderData.GetPartitionId());
|
|
|
|
if (mRetrieveNewNetworkData ||
|
|
SerialNumber::IsGreater(leaderData.GetDataVersion(NetworkData::kFullSet),
|
|
Get<NetworkData::Leader>().GetVersion(NetworkData::kFullSet)))
|
|
{
|
|
IgnoreError(SendDataRequest(aRxInfo.mMessageInfo.GetPeerAddr()));
|
|
}
|
|
|
|
switch (aRxInfo.mMessage.ReadRouteTlv(routeTlvData))
|
|
{
|
|
case kErrorNone:
|
|
VerifyOrExit(routeTlvData.IsAllocated(routerId), error = kErrorParse);
|
|
|
|
if (mRouterTable.IsRouteTlvIdSequenceMoreRecent(routeTlvData))
|
|
{
|
|
SuccessOrExit(error = ProcessRouteTlv(routeTlvData, aRxInfo));
|
|
router = mRouterTable.FindRouterById(routerId);
|
|
OT_ASSERT(router != nullptr);
|
|
}
|
|
shouldUpdateRoutes = true;
|
|
break;
|
|
|
|
case kErrorNotFound:
|
|
break;
|
|
|
|
default:
|
|
ExitNow(error = kErrorParse);
|
|
}
|
|
|
|
if (routerId != mRouterId && !IsRouterIdValid(router->GetNextHop()))
|
|
{
|
|
ResetAdvertiseInterval();
|
|
}
|
|
|
|
break;
|
|
|
|
case kRoleDisabled:
|
|
OT_ASSERT(false);
|
|
}
|
|
|
|
InitNeighbor(*router, aRxInfo);
|
|
router->SetRloc16(sourceAddress);
|
|
router->GetLinkFrameCounters().SetAll(linkFrameCounter);
|
|
router->SetLinkAckFrameCounter(linkFrameCounter);
|
|
router->SetMleFrameCounter(mleFrameCounter);
|
|
router->SetVersion(version);
|
|
router->SetDeviceMode(DeviceMode(DeviceMode::kModeFullThreadDevice | DeviceMode::kModeRxOnWhenIdle |
|
|
DeviceMode::kModeFullNetworkData));
|
|
router->SetLinkQualityOut(LinkQualityForLinkMargin(linkMargin));
|
|
router->SetState(Neighbor::kStateValid);
|
|
router->SetKeySequence(aRxInfo.mKeySequence);
|
|
router->ClearLinkAcceptTimeout();
|
|
|
|
mNeighborTable.Signal(NeighborTable::kRouterAdded, *router);
|
|
|
|
mDelayedSender.RemoveScheduledLinkRequest(*router);
|
|
|
|
if (shouldUpdateRoutes)
|
|
{
|
|
mRouterTable.UpdateRoutes(routeTlvData, routerId);
|
|
}
|
|
|
|
aRxInfo.mClass = RxInfo::kAuthoritativeMessage;
|
|
ProcessKeySequence(aRxInfo);
|
|
|
|
if (aMessageType == kTypeLinkAcceptAndRequest)
|
|
{
|
|
LinkAcceptInfo info;
|
|
|
|
SuccessOrExit(error = aRxInfo.mMessage.ReadChallengeTlv(info.mRxChallenge));
|
|
|
|
switch (aRxInfo.mMessage.ReadTlvRequestTlv(info.mRequestedTlvList))
|
|
{
|
|
case kErrorNone:
|
|
case kErrorNotFound:
|
|
break;
|
|
default:
|
|
ExitNow(error = kErrorParse);
|
|
}
|
|
|
|
info.mExtAddress = router->GetExtAddress();
|
|
info.mRloc16 = router->GetRloc16();
|
|
info.mLinkMargin = Get<Mac::Mac>().ComputeLinkMargin(aRxInfo.mMessage.GetAverageRss());
|
|
|
|
SuccessOrExit(error = SendLinkAccept(info));
|
|
}
|
|
|
|
exit:
|
|
LogProcessError(aMessageType, error);
|
|
}
|
|
|
|
Error Mle::ProcessRouteTlv(const RouteTlv::Data &aRouteTlvData, RxInfo &aRxInfo)
|
|
{
|
|
// This method processes `aRouteTlvData` read from an MLE message.
|
|
//
|
|
// During processing of Route TLV, the entries in the router table
|
|
// may shuffle. This method ensures that the `aRxInfo.mNeighbor`
|
|
// (which indicates the neighbor from which the MLE message was
|
|
// received) is correctly updated to point to the same neighbor
|
|
// (in case `mNeighbor` was pointing to a router entry from the
|
|
// `RouterTable`).
|
|
|
|
Error error = kErrorNone;
|
|
uint16_t neighborRloc16 = kInvalidRloc16;
|
|
|
|
if ((aRxInfo.mNeighbor != nullptr) && Get<RouterTable>().Contains(*aRxInfo.mNeighbor))
|
|
{
|
|
neighborRloc16 = aRxInfo.mNeighbor->GetRloc16();
|
|
}
|
|
|
|
mRouterTable.UpdateRouterIdMask(aRouteTlvData);
|
|
|
|
if (IsAttached() && !mRouterTable.IsAllocated(RouterIdFromRloc16(GetRloc16())))
|
|
{
|
|
// Either we're a router and our own router ID has been removed from the router table,
|
|
// or we're a child and our parent's router ID has been removed from the router table.
|
|
// Detach immediately to minimize time where we're not connected.
|
|
IgnoreError(BecomeDetached());
|
|
error = kErrorNoRoute;
|
|
}
|
|
|
|
if (neighborRloc16 != kInvalidRloc16)
|
|
{
|
|
aRxInfo.mNeighbor = Get<NeighborTable>().FindNeighbor(neighborRloc16);
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
Error Mle::ReadAndProcessRouteTlvOnFtdChild(RxInfo &aRxInfo, uint8_t aParentId)
|
|
{
|
|
// This method reads and processes Route TLV from message on an
|
|
// FTD child if message contains one. It returns `kErrorNone`
|
|
// when successfully processed or if there is no Route TLV in
|
|
// the message.
|
|
//
|
|
// It MUST be used only when device is acting as a child and
|
|
// for a message received from device's current parent.
|
|
|
|
Error error = kErrorNone;
|
|
RouteTlv::Data routeTlvData;
|
|
|
|
VerifyOrExit(IsFullThreadDevice());
|
|
|
|
switch (aRxInfo.mMessage.ReadRouteTlv(routeTlvData))
|
|
{
|
|
case kErrorNone:
|
|
SuccessOrExit(error = ProcessRouteTlv(routeTlvData, aRxInfo));
|
|
mRouterTable.UpdateRouterOnFtdChild(routeTlvData, aParentId);
|
|
mRequestRouteTlv = false;
|
|
break;
|
|
case kErrorNotFound:
|
|
break;
|
|
default:
|
|
ExitNow(error = kErrorParse);
|
|
}
|
|
|
|
exit:
|
|
return error;
|
|
}
|
|
|
|
bool Mle::IsSingleton(void) const
|
|
{
|
|
bool isSingleton = true;
|
|
|
|
VerifyOrExit(IsAttached() && IsRouterRoleAllowed());
|
|
isSingleton = (mRouterTable.GetActiveRouterCount() <= 1);
|
|
|
|
exit:
|
|
return isSingleton;
|
|
}
|
|
|
|
int Mle::ComparePartitions(bool aSingletonA,
|
|
const LeaderData &aLeaderDataA,
|
|
bool aSingletonB,
|
|
const LeaderData &aLeaderDataB)
|
|
{
|
|
int rval = 0;
|
|
|
|
rval = ThreeWayCompare(aLeaderDataA.GetWeighting(), aLeaderDataB.GetWeighting());
|
|
VerifyOrExit(rval == 0);
|
|
|
|
// Not being a singleton is better.
|
|
rval = ThreeWayCompare(!aSingletonA, !aSingletonB);
|
|
VerifyOrExit(rval == 0);
|
|
|
|
rval = ThreeWayCompare(aLeaderDataA.GetPartitionId(), aLeaderDataB.GetPartitionId());
|
|
|
|
exit:
|
|
return rval;
|
|
}
|
|
|
|
Error Mle::HandleAdvertisementOnFtd(RxInfo &aRxInfo, uint16_t aSourceAddress, const LeaderData &aLeaderData)
|
|
{
|
|
// This method processes a received MLE Advertisement message on
|
|
// an FTD device. It is called from `Mle::HandleAdvertisement()`
|
|
// only when device is attached (in child, router, or leader roles)
|
|
// and `IsFullThreadDevice()`.
|
|
//
|
|
// - `aSourceAddress` is the read value from `SourceAddressTlv`.
|
|
// - `aLeaderData` is the read value from `LeaderDataTlv`.
|
|
|
|
Error error = kErrorNone;
|
|
uint8_t linkMargin = Get<Mac::Mac>().ComputeLinkMargin(aRxInfo.mMessage.GetAverageRss());
|
|
RouteTlv::Data routeTlvData;
|
|
bool hasRouteTlv = false;
|
|
Router *router;
|
|
uint8_t routerId;
|
|
uint32_t delay;
|
|
|
|
switch (aRxInfo.mMessage.ReadRouteTlv(routeTlvData))
|
|
{
|
|
case kErrorNone:
|
|
hasRouteTlv = true;
|
|
break;
|
|
case kErrorNotFound:
|
|
break;
|
|
default:
|
|
ExitNow(error = kErrorParse);
|
|
}
|
|
|
|
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
// Handle Partition ID mismatch
|
|
|
|
if (aLeaderData.GetPartitionId() != mLeaderData.GetPartitionId())
|
|
{
|
|
LogNote("Different partition (peer:%lu, local:%lu)", ToUlong(aLeaderData.GetPartitionId()),
|
|
ToUlong(mLeaderData.GetPartitionId()));
|
|
|
|
VerifyOrExit(linkMargin >= kPartitionMergeMinMargin, error = kErrorLinkMarginLow);
|
|
|
|
if (hasRouteTlv && (mPreviousPartitionIdTimeout > 0) && (aLeaderData.GetPartitionId() == mPreviousPartitionId))
|
|
{
|
|
VerifyOrExit(
|
|
SerialNumber::IsGreater(routeTlvData.GetRouterIdSequence(), mPreviousPartitionRouterIdSequence),
|
|
error = kErrorDrop);
|
|
}
|
|
|
|
if (IsChild() && (aRxInfo.mNeighbor == &mParent))
|
|
{
|
|
ExitNow();
|
|
}
|
|
|
|
if (hasRouteTlv &&
|
|
ComparePartitions(routeTlvData.IsSingleton(), aLeaderData, IsSingleton(), mLeaderData) > 0
|
|
#if OPENTHREAD_CONFIG_TIME_SYNC_REQUIRED
|
|
// Allow a better partition if it also enables time sync.
|
|
&& aRxInfo.mMessage.GetTimeSyncSeq() != OT_TIME_SYNC_INVALID_SEQ
|
|
#endif
|
|
)
|
|
{
|
|
mAttacher.Attach(kBetterPartition);
|
|
}
|
|
|
|
ExitNow(error = kErrorDrop);
|
|
}
|
|
|
|
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
// Handle Leader Router ID mismatch
|
|
|
|
if (aLeaderData.GetLeaderRouterId() != GetLeaderId())
|
|
{
|
|
VerifyOrExit(aRxInfo.IsNeighborStateValid());
|
|
|
|
if (!IsChild())
|
|
{
|
|
LogInfo("Leader ID mismatch");
|
|
IgnoreError(BecomeDetached());
|
|
error = kErrorDrop;
|
|
}
|
|
|
|
ExitNow();
|
|
}
|
|
|
|
VerifyOrExit(IsRouterRloc16(aSourceAddress) && hasRouteTlv);
|
|
routerId = RouterIdFromRloc16(aSourceAddress);
|
|
|
|
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
|
|
Get<TimeSync>().HandleTimeSyncMessage(aRxInfo.mMessage);
|
|
#endif
|
|
|
|
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
// Process `RouteTlv`
|
|
|
|
if (aRxInfo.IsNeighborStateValid() && mRouterTable.IsRouteTlvIdSequenceMoreRecent(routeTlvData))
|
|
{
|
|
SuccessOrExit(error = ProcessRouteTlv(routeTlvData, aRxInfo));
|
|
}
|
|
|
|
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
// Update routers as a child
|
|
|
|
if (IsChild())
|
|
{
|
|
if (aRxInfo.mNeighbor == &mParent)
|
|
{
|
|
// MLE Advertisement from parent
|
|
router = &mParent;
|
|
|
|
if (mParent.GetRloc16() != aSourceAddress)
|
|
{
|
|
IgnoreError(BecomeDetached());
|
|
ExitNow(error = kErrorDetached);
|
|
}
|
|
|
|
mRouterTable.UpdateRouterOnFtdChild(routeTlvData, routerId);
|
|
|
|
mRoleTransitioner.DecideWhetherToUpgrade();
|
|
}
|
|
else
|
|
{
|
|
// MLE Advertisement not from parent, but from some other neighboring router
|
|
router = mRouterTable.FindRouterById(routerId);
|
|
VerifyOrExit(router != nullptr);
|
|
|
|
EstablishRouterLinkOnFtdChild(*router, aRxInfo, linkMargin);
|
|
}
|
|
|
|
#if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
|
|
router->SetSelectableAsParent(true);
|
|
#endif
|
|
|
|
router->SetLastHeard(TimerMilli::GetNow());
|
|
|
|
ExitNow();
|
|
}
|
|
|
|
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
// Inform `RoleTransitioner` to decide whether we need to downgrade
|
|
|
|
mRoleTransitioner.DecideWhetherToDowngrade(routerId, routeTlvData);
|
|
|
|
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
// Update routers as a router or leader.
|
|
|
|
router = mRouterTable.FindRouterById(routerId);
|
|
VerifyOrExit(router != nullptr);
|
|
|
|
if (!router->IsStateValid() && aRxInfo.IsNeighborStateValid() && Get<ChildTable>().Contains(*aRxInfo.mNeighbor))
|
|
{
|
|
// The Adv is from a former child that is now acting as a router,
|
|
// we copy the info from child entry and update the RLOC16.
|
|
|
|
*static_cast<Neighbor *>(router) = *aRxInfo.mNeighbor;
|
|
router->SetRloc16(Rloc16FromRouterId(routerId));
|
|
router->SetDeviceMode(DeviceMode(DeviceMode::kModeFullThreadDevice | DeviceMode::kModeRxOnWhenIdle |
|
|
DeviceMode::kModeFullNetworkData));
|
|
|
|
mNeighborTable.Signal(NeighborTable::kRouterAdded, *router);
|
|
|
|
// Change the cache entries associated with the former child
|
|
// from using the old RLOC16 to its new RLOC16.
|
|
Get<AddressResolver>().ReplaceEntriesForRloc16(aRxInfo.mNeighbor->GetRloc16(), router->GetRloc16());
|
|
}
|
|
|
|
// Send unicast link request if no link to router and no
|
|
// unicast/multicast link request in progress
|
|
|
|
if (!router->IsStateValid() && !router->IsStateLinkRequest() && (linkMargin >= kLinkRequestMinMargin) &&
|
|
routeTlvData.IsAllocated(mRouterId))
|
|
{
|
|
InitNeighbor(*router, aRxInfo);
|
|
router->SetState(Neighbor::kStateLinkRequest);
|
|
router->ClearLinkAcceptTimeout();
|
|
delay = GenerateRandomDelay(kMaxLinkRequestDelayOnRouter);
|
|
mDelayedSender.ScheduleLinkRequest(*router, delay);
|
|
ExitNow(error = kErrorNoRoute);
|
|
}
|
|
|
|
router->SetLastHeard(TimerMilli::GetNow());
|
|
|
|
mRouterTable.UpdateRoutes(routeTlvData, routerId);
|
|
|
|
exit:
|
|
if (aRxInfo.mNeighbor && aRxInfo.mNeighbor->GetRloc16() != aSourceAddress)
|
|
{
|
|
RemoveNeighbor(*aRxInfo.mNeighbor);
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
void Mle::EstablishRouterLinkOnFtdChild(Router &aRouter, RxInfo &aRxInfo, uint8_t aLinkMargin)
|
|
{
|
|
// Decide on an FTD child whether to establish a link with a
|
|
// router upon receiving an advertisement from it.
|
|
|
|
uint8_t neighborCount;
|
|
uint32_t minDelay;
|
|
uint32_t maxDelay;
|
|
|
|
VerifyOrExit(!aRouter.IsStateValid() && !aRouter.IsStateLinkRequest());
|
|
|
|
// The first `mChildRouterLinks` are established quickly. After that,
|
|
// the "gradual router link establishment" mechanism is used, which
|
|
// allows `kExtraChildRouterLinks` additional router links to be
|
|
// established, but it is done slowly and over a longer span of time.
|
|
//
|
|
// Gradual router link establishment conditions:
|
|
// - The maximum `neighborCount` limit is not yet reached.
|
|
// - We see Link Quality 2 or better.
|
|
// - Always skipped in the first 5 minutes
|
|
// (`kWaitDurationAfterAttach`) after the device attaches.
|
|
// - The child randomly decides whether to perform/skip this (with a 5%
|
|
// probability, `kProbabilityPercentage`).
|
|
// - If the child decides to send Link Request, a longer random delay
|
|
// window is used, [1.5-10] seconds.
|
|
//
|
|
// Even in a dense network, if the advertisement is received by 500 FTD
|
|
// children with a 5% selection probability, on average, 25 nodes will
|
|
// try to send a Link Request, which will be randomly spread over a
|
|
// [1.5-10] second window.
|
|
//
|
|
// With a 5% probability, on average, it takes 20 trials (20 advertisement
|
|
// receptions for an FTD child to send a Link Request). Advertisements
|
|
// are, on average, ~32 seconds apart, so, on average, a child will try
|
|
// to establish a link in `20 * 32 = 640` seconds (~10 minutes).
|
|
|
|
neighborCount = mRouterTable.GetNeighborCount(kLinkQuality1);
|
|
|
|
if (neighborCount < mChildRouterLinks)
|
|
{
|
|
minDelay = kMinLinkRequestDelayOnChild;
|
|
maxDelay = kMaxLinkRequestDelayOnChild;
|
|
}
|
|
else
|
|
{
|
|
VerifyOrExit(neighborCount < mChildRouterLinks + GradualChildRouterLink::kExtraChildRouterLinks);
|
|
VerifyOrExit(LinkQualityForLinkMargin(aLinkMargin) >= kLinkQuality2);
|
|
VerifyOrExit(GetCurrentAttachDuration() > GradualChildRouterLink::kWaitDurationAfterAttach);
|
|
VerifyOrExit(Random::NonCrypto::GenerateUpToExcluding<uint8_t>(100) <
|
|
GradualChildRouterLink::kProbabilityPercentage);
|
|
|
|
minDelay = GradualChildRouterLink::kMinLinkRequestDelay;
|
|
maxDelay = GradualChildRouterLink::kMaxLinkRequestDelay;
|
|
}
|
|
|
|
InitNeighbor(aRouter, aRxInfo);
|
|
aRouter.SetState(Neighbor::kStateLinkRequest);
|
|
aRouter.ClearLinkAcceptTimeout();
|
|
mDelayedSender.ScheduleLinkRequest(aRouter, Random::NonCrypto::GenerateInClosedRange(minDelay, maxDelay));
|
|
|
|
exit:
|
|
return;
|
|
}
|
|
|
|
void Mle::HandleParentRequest(RxInfo &aRxInfo)
|
|
{
|
|
Error error = kErrorNone;
|
|
uint16_t version;
|
|
uint8_t scanMask;
|
|
Child *child;
|
|
DeviceMode mode;
|
|
uint32_t delay;
|
|
ParentResponseInfo info;
|
|
|
|
Log(kMessageReceive, kTypeParentRequest, aRxInfo.mMessageInfo.GetPeerAddr());
|
|
|
|
VerifyOrExit(IsRouterRoleAllowed());
|
|
VerifyOrExit(!IsDetached() && !IsAttaching());
|
|
|
|
VerifyOrExit(!mDetacher.IsDetaching());
|
|
|
|
VerifyOrExit(mRouterTable.GetLeaderAge() < mNetworkIdTimeout, error = kErrorDrop);
|
|
VerifyOrExit(mRouterTable.GetPathCostToLeader() < kMaxRouteCost, error = kErrorDrop);
|
|
|
|
info.mChildExtAddress.SetFromIid(aRxInfo.mMessageInfo.GetPeerAddr().GetIid());
|
|
|
|
SuccessOrExit(error = aRxInfo.mMessage.ReadVersionTlv(version));
|
|
|
|
SuccessOrExit(error = Tlv::Find<ScanMaskTlv>(aRxInfo.mMessage, scanMask));
|
|
|
|
switch (mRole)
|
|
{
|
|
case kRoleDisabled:
|
|
case kRoleDetached:
|
|
ExitNow();
|
|
|
|
case kRoleChild:
|
|
VerifyOrExit(ScanMaskTlv::IsEndDeviceFlagSet(scanMask));
|
|
VerifyOrExit(mRouterTable.GetActiveRouterCount() < kMaxRouters, error = kErrorDrop);
|
|
break;
|
|
|
|
case kRoleRouter:
|
|
case kRoleLeader:
|
|
VerifyOrExit(ScanMaskTlv::IsRouterFlagSet(scanMask));
|
|
break;
|
|
}
|
|
|
|
SuccessOrExit(error = aRxInfo.mMessage.ReadChallengeTlv(info.mRxChallenge));
|
|
|
|
child = mChildTable.FindChild(info.mChildExtAddress, Child::kInStateAnyExceptInvalid);
|
|
|
|
if (child == nullptr)
|
|
{
|
|
VerifyOrExit((child = mChildTable.GetNewChild()) != nullptr, error = kErrorNoBufs);
|
|
|
|
InitNeighbor(*child, aRxInfo);
|
|
child->SetState(Neighbor::kStateParentRequest);
|
|
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
|
|
child->SetTimeSyncEnabled(Tlv::Find<TimeRequestTlv>(aRxInfo.mMessage, nullptr, 0) == kErrorNone);
|
|
#endif
|
|
if (aRxInfo.mMessage.ReadModeTlv(mode) == kErrorNone)
|
|
{
|
|
child->SetDeviceMode(mode);
|
|
child->SetVersion(version);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
uint32_t durSinceLastHeard = TimerMilli::GetNow() - child->GetLastHeard();
|
|
|
|
VerifyOrExit(durSinceLastHeard >= kParentRequestDuplicateTimeout, error = kErrorDuplicated);
|
|
}
|
|
|
|
if (!child->IsStateValidOrRestoring())
|
|
{
|
|
child->SetLastHeard(TimerMilli::GetNow());
|
|
child->SetTimeout(Time::MsecToSec(kChildIdRequestTimeout));
|
|
}
|
|
|
|
aRxInfo.mClass = RxInfo::kPeerMessage;
|
|
ProcessKeySequence(aRxInfo);
|
|
|
|
delay = GenerateRandomDelay(!ScanMaskTlv::IsEndDeviceFlagSet(scanMask) ? kParentResponseMaxDelayRouters
|
|
: kParentResponseMaxDelayAll);
|
|
mDelayedSender.ScheduleParentResponse(info, delay);
|
|
|
|
exit:
|
|
LogProcessError(kTypeParentRequest, error);
|
|
}
|
|
|
|
bool Mle::HasNeighborWithGoodLinkQuality(void) const
|
|
{
|
|
bool haveNeighbor = true;
|
|
uint8_t linkMargin;
|
|
|
|
linkMargin = Get<Mac::Mac>().ComputeLinkMargin(mParent.GetLinkInfo().GetLastRss());
|
|
|
|
if (linkMargin >= kLinkRequestMinMargin)
|
|
{
|
|
ExitNow();
|
|
}
|
|
|
|
for (const Router &router : Get<RouterTable>())
|
|
{
|
|
if (!router.IsStateValid())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
linkMargin = Get<Mac::Mac>().ComputeLinkMargin(router.GetLinkInfo().GetLastRss());
|
|
|
|
if (linkMargin >= kLinkRequestMinMargin)
|
|
{
|
|
ExitNow();
|
|
}
|
|
}
|
|
|
|
haveNeighbor = false;
|
|
|
|
exit:
|
|
return haveNeighbor;
|
|
}
|
|
|
|
void Mle::HandleTimeTick(void)
|
|
{
|
|
VerifyOrExit(IsFullThreadDevice(), Get<TimeTicker>().UnregisterReceiver(TimeTicker::kMle));
|
|
|
|
if (mPreviousPartitionIdTimeout > 0)
|
|
{
|
|
mPreviousPartitionIdTimeout--;
|
|
}
|
|
|
|
if (mAlternateRloc16Timeout > 0)
|
|
{
|
|
mAlternateRloc16Timeout--;
|
|
|
|
if (mAlternateRloc16Timeout == 0)
|
|
{
|
|
ClearAlternateRloc16();
|
|
}
|
|
}
|
|
|
|
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
// Role transitions
|
|
|
|
mRoleTransitioner.HandleTimeTick();
|
|
|
|
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
// Check Leader's age
|
|
|
|
switch (mRole)
|
|
{
|
|
case kRoleDisabled:
|
|
case kRoleDetached:
|
|
case kRoleLeader:
|
|
break;
|
|
|
|
case kRoleChild:
|
|
if (!IsRouterRoleAllowed())
|
|
{
|
|
break;
|
|
}
|
|
|
|
OT_FALL_THROUGH;
|
|
|
|
case kRoleRouter:
|
|
LogDebg("Leader age %lu", ToUlong(mRouterTable.GetLeaderAge()));
|
|
|
|
if ((mRouterTable.GetActiveRouterCount() > 0) && (mRouterTable.GetLeaderAge() >= mNetworkIdTimeout))
|
|
{
|
|
LogInfo("Leader age timeout");
|
|
mAttacher.Attach(kSamePartition);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
// Update `ChildTable`
|
|
|
|
for (Child &child : Get<ChildTable>().Iterate(Child::kInStateAnyExceptInvalid))
|
|
{
|
|
uint32_t timeout = 0;
|
|
|
|
switch (child.GetState())
|
|
{
|
|
case Neighbor::kStateInvalid:
|
|
continue;
|
|
|
|
case Neighbor::kStateParentRequest:
|
|
case Neighbor::kStateChildIdRequest:
|
|
case Neighbor::kStateValid:
|
|
case Neighbor::kStateRestored:
|
|
case Neighbor::kStateChildUpdateRequest:
|
|
timeout = Time::SecToMsec(child.GetTimeout());
|
|
break;
|
|
|
|
case Neighbor::kStateParentResponse:
|
|
case Neighbor::kStateLinkRequest:
|
|
OT_ASSERT(false);
|
|
}
|
|
|
|
#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
|
|
if (child.IsCslSynchronized() &&
|
|
TimerMilli::GetNow() - child.GetCslLastHeard() >= Time::SecToMsec(child.GetCslTimeout()))
|
|
{
|
|
LogInfo("Child 0x%04x CSL synchronization expired", child.GetRloc16());
|
|
child.SetCslSynchronized(false);
|
|
Get<CslTxScheduler>().Update();
|
|
}
|
|
#endif
|
|
|
|
if (TimerMilli::GetNow() - child.GetLastHeard() >= timeout)
|
|
{
|
|
LogInfo("Child 0x%04x timeout expired", child.GetRloc16());
|
|
RemoveNeighbor(child);
|
|
}
|
|
else if (IsRouterOrLeader() && child.IsStateRestored())
|
|
{
|
|
IgnoreError(SendChildUpdateRequestToChild(child));
|
|
}
|
|
}
|
|
|
|
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
// Update `RouterTable`
|
|
|
|
for (Router &router : Get<RouterTable>())
|
|
{
|
|
uint32_t age;
|
|
|
|
if (router.GetRloc16() == GetRloc16())
|
|
{
|
|
router.SetLastHeard(TimerMilli::GetNow());
|
|
continue;
|
|
}
|
|
|
|
age = TimerMilli::GetNow() - router.GetLastHeard();
|
|
|
|
#if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
|
|
router.DecrementParentReselectTimeout();
|
|
|
|
if (age >= kMaxNeighborAge)
|
|
{
|
|
router.SetSelectableAsParent(false);
|
|
}
|
|
#endif
|
|
if (router.IsStateValid())
|
|
{
|
|
// Neighbor router age and link recovery
|
|
//
|
|
// If the device is an FTD child and has more than
|
|
// `mChildRouterLinks` neighbors, it uses a longer age,
|
|
// `kMaxNeighborAgeOnChild`, and removes the neighboring
|
|
// router upon expiration without trying to re-establish
|
|
// its link with it.
|
|
//
|
|
// Otherwise, if the device itself is a router, or it is an
|
|
// FTD child with `mChildRouterLinks` or fewer neighbors,
|
|
// it uses a shorter `kMaxNeighborAge`. Upon expiration, it
|
|
// tries to re-establish its link with the neighboring router.
|
|
|
|
if (IsChild() && (mRouterTable.GetNeighborCount(kLinkQuality1) > mChildRouterLinks))
|
|
{
|
|
if (age >= kMaxNeighborAgeOnChild)
|
|
{
|
|
LogInfo("No Adv from router 0x%04x - removing router", router.GetRloc16());
|
|
mDelayedSender.RemoveScheduledLinkRequest(router);
|
|
RemoveNeighbor(router);
|
|
continue;
|
|
}
|
|
}
|
|
else if (age >= kMaxNeighborAge)
|
|
{
|
|
// We send a Link Request every time tick (second), up to
|
|
// max attempts. Each transmission is randomly delayed
|
|
// (one-second window). After the last attempt, we wait for
|
|
// the "Link Accept" timeout (~3 seconds) before removing the
|
|
// neighboring router.
|
|
|
|
if (!mDelayedSender.HasAnyScheduledLinkRequest(router) && !router.IsWaitingForLinkAccept())
|
|
{
|
|
LogInfo("No Adv from router 0x%04x - sending Link Request", router.GetRloc16());
|
|
router.SetLinkRequestAttemptsToMax();
|
|
}
|
|
|
|
if (router.HasRemainingLinkRequestAttempts())
|
|
{
|
|
router.DecrementLinkRequestAttempts();
|
|
mDelayedSender.ScheduleLinkRequest(router, GenerateRandomDelay(kMaxLinkRequestDelayOnRouter));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (router.IsStateLinkRequest() && !mDelayedSender.HasAnyScheduledLinkRequest(router) &&
|
|
!router.IsWaitingForLinkAccept())
|
|
{
|
|
LogInfo("Router 0x%04x - Failed to schedule/send Link Request", router.GetRloc16());
|
|
RemoveNeighbor(router);
|
|
continue;
|
|
}
|
|
|
|
if (router.IsWaitingForLinkAccept() && (router.DecrementLinkAcceptTimeout() == 0))
|
|
{
|
|
LogInfo("Router 0x%04x - Link Accept timeout expired", router.GetRloc16());
|
|
RemoveNeighbor(router);
|
|
continue;
|
|
}
|
|
|
|
if (IsLeader() && (mRouterTable.FindNextHopTowards(router) == nullptr) &&
|
|
(mRouterTable.GetLinkCost(router) >= kMaxRouteCost) && (age >= kMaxLeaderToRouterTimeout))
|
|
{
|
|
LogInfo("Router 0x%04x ID timeout expired (no route)", router.GetRloc16());
|
|
IgnoreError(mRouterTable.Release(router.GetRouterId()));
|
|
}
|
|
}
|
|
|
|
mRouterTable.HandleTimeTick();
|
|
|
|
SynchronizeChildNetworkData();
|
|
|
|
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
|
|
if (IsRouterOrLeader())
|
|
{
|
|
Get<TimeSync>().ProcessTimeSync();
|
|
}
|
|
#endif
|
|
|
|
exit:
|
|
return;
|
|
}
|
|
|
|
void Mle::SendParentResponse(const ParentResponseInfo &aInfo)
|
|
{
|
|
Error error = kErrorNone;
|
|
TxMessage *message = nullptr;
|
|
Child *child;
|
|
Ip6::Address destination;
|
|
|
|
child = mChildTable.FindChild(aInfo.mChildExtAddress, Child::kInStateAnyExceptInvalid);
|
|
VerifyOrExit(child != nullptr);
|
|
|
|
VerifyOrExit((message = NewMleMessage(kCommandParentResponse)) != nullptr, error = kErrorNoBufs);
|
|
message->SetDirectTransmission();
|
|
|
|
SuccessOrExit(error = message->AppendSourceAddressAndLeaderDataTlvs());
|
|
SuccessOrExit(error = message->AppendLinkAndMleFrameCounterTlvs());
|
|
SuccessOrExit(error = message->AppendResponseTlv(aInfo.mRxChallenge));
|
|
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
|
|
if (child->IsTimeSyncEnabled())
|
|
{
|
|
SuccessOrExit(error = message->AppendTimeParameterTlv());
|
|
}
|
|
#endif
|
|
#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
|
|
if (child->IsThreadVersionCslCapable())
|
|
{
|
|
SuccessOrExit(error = message->AppendCslClockAccuracyTlv());
|
|
}
|
|
#endif
|
|
child->GenerateChallenge();
|
|
SuccessOrExit(error = message->AppendChallengeTlv(child->GetChallenge()));
|
|
SuccessOrExit(error = message->AppendLinkMarginTlv(child->GetLinkInfo().GetLinkMargin()));
|
|
SuccessOrExit(error = message->AppendConnectivityTlv());
|
|
SuccessOrExit(error = message->AppendVersionTlv());
|
|
|
|
destination.InitAsLinkLocalAddress(aInfo.mChildExtAddress);
|
|
|
|
SuccessOrExit(error = message->SendTo(destination));
|
|
|
|
Log(kMessageSend, kTypeParentResponse, destination);
|
|
|
|
exit:
|
|
FreeMessageOnError(message, error);
|
|
LogSendError(kTypeParentResponse, error);
|
|
}
|
|
|
|
Error Mle::ProcessAddressRegistrationTlv(RxInfo &aRxInfo, Child &aChild)
|
|
{
|
|
Error error;
|
|
OffsetRange offsetRange;
|
|
uint8_t count = 0;
|
|
uint8_t storedCount = 0;
|
|
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
|
|
Child::Ip6AddressArray oldMlrRegisteredAddresses;
|
|
#endif
|
|
|
|
OT_UNUSED_VARIABLE(storedCount);
|
|
|
|
SuccessOrExit(error = Tlv::FindTlvValueOffsetRange(aRxInfo.mMessage, Tlv::kAddressRegistration, offsetRange));
|
|
|
|
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
|
|
aChild.GetAllMlrRegisteredAddresses(oldMlrRegisteredAddresses);
|
|
#endif
|
|
|
|
aChild.ClearIp6Addresses();
|
|
|
|
while (!offsetRange.IsEmpty())
|
|
{
|
|
uint8_t controlByte;
|
|
Ip6::Address address;
|
|
|
|
// Read out the control byte (first byte in entry)
|
|
SuccessOrExit(error = aRxInfo.mMessage.ReadAndAdvance(offsetRange, controlByte));
|
|
count++;
|
|
|
|
address.Clear();
|
|
|
|
if (AddressRegistrationTlv::IsEntryCompressed(controlByte))
|
|
{
|
|
// Compressed entry contains IID with the 64-bit prefix
|
|
// determined from 6LoWPAN context identifier (from
|
|
// the control byte).
|
|
|
|
uint8_t contextId = AddressRegistrationTlv::GetContextId(controlByte);
|
|
Lowpan::Context context;
|
|
|
|
IgnoreError(aRxInfo.mMessage.ReadAndAdvance(offsetRange, address.GetIid()));
|
|
|
|
Get<NetworkData::Leader>().FindContextForId(contextId, context);
|
|
|
|
if (!context.IsValid())
|
|
{
|
|
LogWarn("Failed to get context %u for compressed address from child 0x%04x", contextId,
|
|
aChild.GetRloc16());
|
|
continue;
|
|
}
|
|
|
|
address.SetPrefix(context.GetPrefix());
|
|
}
|
|
else
|
|
{
|
|
// Uncompressed entry contains the full IPv6 address.
|
|
|
|
IgnoreError(aRxInfo.mMessage.ReadAndAdvance(offsetRange, address));
|
|
}
|
|
|
|
error = aChild.AddIp6Address(address);
|
|
|
|
if (error == kErrorNone)
|
|
{
|
|
storedCount++;
|
|
LogInfo("Child 0x%04x IPv6 address[%u]=%s", aChild.GetRloc16(), storedCount,
|
|
address.ToString().AsCString());
|
|
}
|
|
else
|
|
{
|
|
LogWarnOnError(error, "add IPv6 address %s to child 0x%04x", address.ToString().AsCString(),
|
|
aChild.GetRloc16());
|
|
}
|
|
|
|
if (address.IsMulticast())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// We check if the same address is in-use by another child, if so
|
|
// remove it. This implements "last-in wins" duplicate address
|
|
// resolution policy.
|
|
//
|
|
// Duplicate addresses can occur if a previously attached child
|
|
// attaches to same parent again (after a reset, memory wipe) using
|
|
// a new random extended address before the old entry in the child
|
|
// table is timed out and then trying to register its globally unique
|
|
// IPv6 address as the new child.
|
|
|
|
for (Child &child : Get<ChildTable>().Iterate(Child::kInStateValidOrRestoring))
|
|
{
|
|
if (&child == &aChild)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
IgnoreError(child.RemoveIp6Address(address));
|
|
}
|
|
|
|
// Clear EID-to-RLOC cache for the unicast address registered by the child.
|
|
Get<AddressResolver>().RemoveEntryForAddress(address);
|
|
}
|
|
|
|
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
|
|
Get<Mlr::Manager>().UpdateChildRegistrations(aChild, oldMlrRegisteredAddresses);
|
|
#endif
|
|
|
|
if (count == 0)
|
|
{
|
|
LogInfo("Child 0x%04x has no registered IPv6 address", aChild.GetRloc16());
|
|
}
|
|
else
|
|
{
|
|
LogInfo("Child 0x%04x has %u registered IPv6 address%s, %u address%s stored", aChild.GetRloc16(), count,
|
|
(count == 1) ? "" : "es", storedCount, (storedCount == 1) ? "" : "es");
|
|
}
|
|
|
|
error = kErrorNone;
|
|
|
|
exit:
|
|
return error;
|
|
}
|
|
|
|
bool Mle::IsMessageMleSubType(const Message &aMessage) { return aMessage.IsSubTypeMle(); }
|
|
|
|
bool Mle::IsMessageChildUpdateRequest(const Message &aMessage)
|
|
{
|
|
return aMessage.IsMleCommand(kCommandChildUpdateRequest);
|
|
}
|
|
|
|
void Mle::HandleChildIdRequest(RxInfo &aRxInfo)
|
|
{
|
|
Error error = kErrorNone;
|
|
Mac::ExtAddress extAddr;
|
|
uint16_t version;
|
|
uint32_t linkFrameCounter;
|
|
uint32_t mleFrameCounter;
|
|
DeviceMode mode;
|
|
uint32_t timeout;
|
|
TlvList tlvList;
|
|
MeshCoP::Timestamp timestamp;
|
|
Child *child;
|
|
Router *router;
|
|
uint16_t supervisionInterval;
|
|
|
|
Log(kMessageReceive, kTypeChildIdRequest, aRxInfo.mMessageInfo.GetPeerAddr());
|
|
|
|
VerifyOrExit(IsRouterRoleAllowed(), error = kErrorInvalidState);
|
|
|
|
VerifyOrExit(IsAttached(), error = kErrorInvalidState);
|
|
|
|
extAddr.SetFromIid(aRxInfo.mMessageInfo.GetPeerAddr().GetIid());
|
|
|
|
child = mChildTable.FindChild(extAddr, Child::kInStateAnyExceptInvalid);
|
|
VerifyOrExit(child != nullptr, error = kErrorAlready);
|
|
|
|
SuccessOrExit(error = aRxInfo.mMessage.ReadVersionTlv(version));
|
|
|
|
SuccessOrExit(error = aRxInfo.mMessage.ReadAndMatchResponseTlvWith(child->GetChallenge()));
|
|
|
|
Get<MeshForwarder>().RemoveMessagesForChild(*child, IsMessageMleSubType);
|
|
|
|
SuccessOrExit(error = aRxInfo.mMessage.ReadFrameCounterTlvs(linkFrameCounter, mleFrameCounter));
|
|
|
|
SuccessOrExit(error = aRxInfo.mMessage.ReadModeTlv(mode));
|
|
|
|
SuccessOrExit(error = Tlv::Find<TimeoutTlv>(aRxInfo.mMessage, timeout));
|
|
|
|
SuccessOrExit(error = aRxInfo.mMessage.ReadTlvRequestTlv(tlvList));
|
|
|
|
switch (Tlv::Find<SupervisionIntervalTlv>(aRxInfo.mMessage, supervisionInterval))
|
|
{
|
|
case kErrorNone:
|
|
tlvList.Add(Tlv::kSupervisionInterval);
|
|
break;
|
|
case kErrorNotFound:
|
|
supervisionInterval = (version <= kThreadVersion1p3) ? kChildSupervisionDefaultIntervalForOlderVersion : 0;
|
|
break;
|
|
default:
|
|
ExitNow(error = kErrorParse);
|
|
}
|
|
|
|
switch (Tlv::Find<ActiveTimestampTlv>(aRxInfo.mMessage, timestamp))
|
|
{
|
|
case kErrorNone:
|
|
if (timestamp == Get<MeshCoP::ActiveDatasetManager>().GetTimestamp())
|
|
{
|
|
break;
|
|
}
|
|
|
|
OT_FALL_THROUGH;
|
|
|
|
case kErrorNotFound:
|
|
tlvList.Add(Tlv::kActiveDataset);
|
|
break;
|
|
|
|
default:
|
|
ExitNow(error = kErrorParse);
|
|
}
|
|
|
|
switch (Tlv::Find<PendingTimestampTlv>(aRxInfo.mMessage, timestamp))
|
|
{
|
|
case kErrorNone:
|
|
if (timestamp == Get<MeshCoP::PendingDatasetManager>().GetTimestamp())
|
|
{
|
|
break;
|
|
}
|
|
|
|
OT_FALL_THROUGH;
|
|
|
|
case kErrorNotFound:
|
|
tlvList.Add(Tlv::kPendingDataset);
|
|
break;
|
|
|
|
default:
|
|
ExitNow(error = kErrorParse);
|
|
}
|
|
|
|
VerifyOrExit(tlvList.GetLength() <= Child::kMaxRequestTlvs, error = kErrorParse);
|
|
|
|
if (!mode.IsFullThreadDevice())
|
|
{
|
|
SuccessOrExit(error = ProcessAddressRegistrationTlv(aRxInfo, *child));
|
|
}
|
|
|
|
router = mRouterTable.FindRouter(extAddr);
|
|
|
|
if (router != nullptr)
|
|
{
|
|
RemoveNeighbor(*router);
|
|
}
|
|
|
|
if (!child->IsStateValid())
|
|
{
|
|
child->SetState(Neighbor::kStateChildIdRequest);
|
|
}
|
|
else
|
|
{
|
|
RemoveNeighbor(*child);
|
|
}
|
|
|
|
child->SetLastHeard(TimerMilli::GetNow());
|
|
child->GetLinkFrameCounters().SetAll(linkFrameCounter);
|
|
child->SetLinkAckFrameCounter(linkFrameCounter);
|
|
child->SetMleFrameCounter(mleFrameCounter);
|
|
child->SetKeySequence(aRxInfo.mKeySequence);
|
|
child->SetDeviceMode(mode);
|
|
child->SetVersion(version);
|
|
child->GetLinkInfo().AddRss(aRxInfo.mMessage.GetAverageRss());
|
|
child->SetTimeout(timeout);
|
|
child->SetSupervisionInterval(supervisionInterval);
|
|
#if OPENTHREAD_CONFIG_MULTI_RADIO
|
|
child->ClearLastRxFragmentTag();
|
|
#endif
|
|
|
|
child->SetNetworkDataVersion(mLeaderData.GetDataVersion(mode.GetNetworkDataType()));
|
|
|
|
// We already checked above that `tlvList` will fit in
|
|
// `child` entry (with `Child::kMaxRequestTlvs` TLVs).
|
|
|
|
child->ClearRequestTlvs();
|
|
|
|
for (uint8_t index = 0; index < tlvList.GetLength(); index++)
|
|
{
|
|
child->SetRequestTlv(index, tlvList[index]);
|
|
}
|
|
|
|
aRxInfo.mClass = RxInfo::kAuthoritativeMessage;
|
|
ProcessKeySequence(aRxInfo);
|
|
|
|
switch (mRole)
|
|
{
|
|
case kRoleChild:
|
|
child->SetState(Neighbor::kStateChildIdRequest);
|
|
IgnoreError(BecomeRouter(kReasonHaveChildIdRequest));
|
|
break;
|
|
|
|
case kRoleRouter:
|
|
case kRoleLeader:
|
|
SuccessOrExit(error = SendChildIdResponse(*child));
|
|
break;
|
|
|
|
case kRoleDisabled:
|
|
case kRoleDetached:
|
|
OT_ASSERT(false);
|
|
}
|
|
|
|
exit:
|
|
LogProcessError(kTypeChildIdRequest, error);
|
|
}
|
|
|
|
void Mle::HandleChildUpdateRequestOnParent(RxInfo &aRxInfo)
|
|
{
|
|
Error error = kErrorNone;
|
|
Mac::ExtAddress extAddr;
|
|
DeviceMode mode;
|
|
LeaderData leaderData;
|
|
uint32_t timeout;
|
|
uint16_t supervisionInterval;
|
|
Child *child;
|
|
DeviceMode oldMode;
|
|
TlvList requestedTlvList;
|
|
ChildUpdateResponseInfo info;
|
|
bool childDidChange = false;
|
|
|
|
Log(kMessageReceive, kTypeChildUpdateRequestOfChild, aRxInfo.mMessageInfo.GetPeerAddr());
|
|
|
|
VerifyOrExit(!mDetacher.IsDetaching());
|
|
|
|
info.mDestination = aRxInfo.mMessageInfo.GetPeerAddr();
|
|
|
|
SuccessOrExit(error = aRxInfo.mMessage.ReadModeTlv(mode));
|
|
|
|
switch (aRxInfo.mMessage.ReadChallengeTlv(info.mChallenge))
|
|
{
|
|
case kErrorNone:
|
|
info.mTlvList.Add(Tlv::kResponse);
|
|
break;
|
|
case kErrorNotFound:
|
|
info.mChallenge.Clear();
|
|
break;
|
|
default:
|
|
ExitNow(error = kErrorParse);
|
|
}
|
|
|
|
info.mTlvList.Add(Tlv::kSourceAddress);
|
|
|
|
extAddr.SetFromIid(aRxInfo.mMessageInfo.GetPeerAddr().GetIid());
|
|
child = mChildTable.FindChild(extAddr, Child::kInStateAnyExceptInvalid);
|
|
|
|
if (child == nullptr)
|
|
{
|
|
// For invalid non-sleepy child, send Child Update Response with
|
|
// Status TLV (error).
|
|
if (mode.IsRxOnWhenIdle())
|
|
{
|
|
IgnoreError(SendChildUpdateRejectResponse(info));
|
|
}
|
|
|
|
ExitNow();
|
|
}
|
|
|
|
// Ignore "Child Update Request" from a child that is present in the
|
|
// child table but it is not yet in valid state. For example, a
|
|
// child which is being restored (due to parent reset) or is in the
|
|
// middle of the attach process (in `kStateParentRequest` or
|
|
// `kStateChildIdRequest`).
|
|
|
|
VerifyOrExit(child->IsStateValid());
|
|
|
|
oldMode = child->GetDeviceMode();
|
|
child->SetDeviceMode(mode);
|
|
|
|
info.mTlvList.Add(Tlv::kMode);
|
|
info.mTlvList.Add(Tlv::kLinkMargin);
|
|
|
|
// Parent MUST include Leader Data TLV in Child Update Response
|
|
info.mTlvList.Add(Tlv::kLeaderData);
|
|
|
|
if (!info.mChallenge.IsEmpty())
|
|
{
|
|
info.mTlvList.Add(Tlv::kMleFrameCounter);
|
|
info.mTlvList.Add(Tlv::kLinkFrameCounter);
|
|
}
|
|
|
|
switch (ProcessAddressRegistrationTlv(aRxInfo, *child))
|
|
{
|
|
case kErrorNone:
|
|
info.mTlvList.Add(Tlv::kAddressRegistration);
|
|
break;
|
|
case kErrorNotFound:
|
|
break;
|
|
default:
|
|
ExitNow(error = kErrorParse);
|
|
}
|
|
|
|
switch (aRxInfo.mMessage.ReadLeaderDataTlv(leaderData))
|
|
{
|
|
case kErrorNone:
|
|
child->SetNetworkDataVersion(leaderData.GetDataVersion(child->GetNetworkDataType()));
|
|
break;
|
|
case kErrorNotFound:
|
|
break;
|
|
default:
|
|
ExitNow(error = kErrorParse);
|
|
}
|
|
|
|
switch (Tlv::Find<TimeoutTlv>(aRxInfo.mMessage, timeout))
|
|
{
|
|
case kErrorNone:
|
|
if (child->GetTimeout() != timeout)
|
|
{
|
|
child->SetTimeout(timeout);
|
|
childDidChange = true;
|
|
}
|
|
|
|
info.mTlvList.Add(Tlv::kTimeout);
|
|
break;
|
|
|
|
case kErrorNotFound:
|
|
break;
|
|
|
|
default:
|
|
ExitNow(error = kErrorParse);
|
|
}
|
|
|
|
switch (Tlv::Find<SupervisionIntervalTlv>(aRxInfo.mMessage, supervisionInterval))
|
|
{
|
|
case kErrorNone:
|
|
info.mTlvList.Add(Tlv::kSupervisionInterval);
|
|
break;
|
|
|
|
case kErrorNotFound:
|
|
supervisionInterval =
|
|
(child->GetVersion() <= kThreadVersion1p3) ? kChildSupervisionDefaultIntervalForOlderVersion : 0;
|
|
break;
|
|
|
|
default:
|
|
ExitNow(error = kErrorParse);
|
|
}
|
|
|
|
child->SetSupervisionInterval(supervisionInterval);
|
|
|
|
switch (aRxInfo.mMessage.ReadTlvRequestTlv(requestedTlvList))
|
|
{
|
|
case kErrorNone:
|
|
info.mTlvList.AddElementsFrom(requestedTlvList);
|
|
break;
|
|
case kErrorNotFound:
|
|
break;
|
|
default:
|
|
ExitNow(error = kErrorParse);
|
|
}
|
|
|
|
#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
|
|
if (child->IsCslSynchronized())
|
|
{
|
|
ChannelTlvValue cslChannelTlvValue;
|
|
uint32_t cslTimeout;
|
|
|
|
switch (Tlv::Find<CslTimeoutTlv>(aRxInfo.mMessage, cslTimeout))
|
|
{
|
|
case kErrorNone:
|
|
child->SetCslTimeout(cslTimeout);
|
|
// MUST include CSL accuracy TLV when request includes CSL timeout
|
|
info.mTlvList.Add(Tlv::kCslClockAccuracy);
|
|
break;
|
|
case kErrorNotFound:
|
|
break;
|
|
default:
|
|
ExitNow(error = kErrorNone);
|
|
}
|
|
|
|
if (Tlv::Find<CslChannelTlv>(aRxInfo.mMessage, cslChannelTlvValue) == kErrorNone)
|
|
{
|
|
// Special value of zero is used to indicate that
|
|
// CSL channel is not specified.
|
|
child->SetCslChannel(static_cast<uint8_t>(cslChannelTlvValue.GetChannel()));
|
|
}
|
|
}
|
|
#endif // OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
|
|
|
|
child->SetLastHeard(TimerMilli::GetNow());
|
|
|
|
if (oldMode != child->GetDeviceMode())
|
|
{
|
|
LogNote("Child 0x%04x mode change 0x%02x -> 0x%02x [%s]", child->GetRloc16(), oldMode.Get(),
|
|
child->GetDeviceMode().Get(), child->GetDeviceMode().ToString().AsCString());
|
|
|
|
childDidChange = true;
|
|
|
|
#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
|
|
if (child->IsRxOnWhenIdle())
|
|
{
|
|
// Clear CSL synchronization state
|
|
child->SetCslSynchronized(false);
|
|
}
|
|
#endif
|
|
|
|
// The `IndirectSender::HandleChildModeChange()` needs to happen
|
|
// after "Child Update" message is fully parsed to ensure that
|
|
// any registered IPv6 addresses included in the "Child Update"
|
|
// are added to the child.
|
|
|
|
Get<IndirectSender>().HandleChildModeChange(*child, oldMode);
|
|
}
|
|
|
|
if (childDidChange)
|
|
{
|
|
IgnoreError(mChildTable.StoreChild(*child));
|
|
}
|
|
|
|
#if OPENTHREAD_CONFIG_MULTI_RADIO
|
|
// We clear the fragment tag only if the "Child Update Request" is
|
|
// from a detached child trying to restore its link with its
|
|
// parent which is indicated by the presence of Challenge TLV in
|
|
// the message.
|
|
if (!info.mChallenge.IsEmpty())
|
|
{
|
|
child->ClearLastRxFragmentTag();
|
|
}
|
|
#endif
|
|
|
|
SendChildUpdateResponseToChild(child, info);
|
|
|
|
aRxInfo.mClass = RxInfo::kPeerMessage;
|
|
|
|
exit:
|
|
LogProcessError(kTypeChildUpdateRequestOfChild, error);
|
|
}
|
|
|
|
void Mle::HandleChildUpdateResponseOnParent(RxInfo &aRxInfo)
|
|
{
|
|
Error error = kErrorNone;
|
|
uint16_t sourceAddress;
|
|
uint32_t timeout;
|
|
RxChallenge response;
|
|
uint8_t status;
|
|
uint32_t linkFrameCounter;
|
|
uint32_t mleFrameCounter;
|
|
LeaderData leaderData;
|
|
Child *child;
|
|
|
|
if ((aRxInfo.mNeighbor == nullptr) || IsRouterRloc16(aRxInfo.mNeighbor->GetRloc16()) ||
|
|
!Get<ChildTable>().Contains(*aRxInfo.mNeighbor))
|
|
{
|
|
Log(kMessageReceive, kTypeChildUpdateResponseOfUnknownChild, aRxInfo.mMessageInfo.GetPeerAddr());
|
|
ExitNow(error = kErrorNotFound);
|
|
}
|
|
|
|
child = static_cast<Child *>(aRxInfo.mNeighbor);
|
|
|
|
switch (aRxInfo.mMessage.ReadResponseTlv(response))
|
|
{
|
|
case kErrorNone:
|
|
VerifyOrExit(response == child->GetChallenge(), error = kErrorSecurity);
|
|
break;
|
|
case kErrorNotFound:
|
|
VerifyOrExit(child->IsStateValid(), error = kErrorSecurity);
|
|
response.Clear();
|
|
break;
|
|
default:
|
|
ExitNow(error = kErrorNone);
|
|
}
|
|
|
|
Log(kMessageReceive, kTypeChildUpdateResponseOfChild, aRxInfo.mMessageInfo.GetPeerAddr(), child->GetRloc16());
|
|
|
|
switch (Tlv::Find<SourceAddressTlv>(aRxInfo.mMessage, sourceAddress))
|
|
{
|
|
case kErrorNone:
|
|
if (child->GetRloc16() != sourceAddress)
|
|
{
|
|
RemoveNeighbor(*child);
|
|
ExitNow();
|
|
}
|
|
|
|
break;
|
|
|
|
case kErrorNotFound:
|
|
break;
|
|
|
|
default:
|
|
ExitNow(error = kErrorParse);
|
|
}
|
|
|
|
switch (Tlv::Find<StatusTlv>(aRxInfo.mMessage, status))
|
|
{
|
|
case kErrorNone:
|
|
VerifyOrExit(status != kStatusError, RemoveNeighbor(*child));
|
|
break;
|
|
case kErrorNotFound:
|
|
break;
|
|
default:
|
|
ExitNow(error = kErrorParse);
|
|
}
|
|
|
|
switch (Tlv::Find<LinkFrameCounterTlv>(aRxInfo.mMessage, linkFrameCounter))
|
|
{
|
|
case kErrorNone:
|
|
child->GetLinkFrameCounters().SetAll(linkFrameCounter);
|
|
child->SetLinkAckFrameCounter(linkFrameCounter);
|
|
break;
|
|
case kErrorNotFound:
|
|
break;
|
|
default:
|
|
ExitNow(error = kErrorParse);
|
|
}
|
|
|
|
switch (Tlv::Find<MleFrameCounterTlv>(aRxInfo.mMessage, mleFrameCounter))
|
|
{
|
|
case kErrorNone:
|
|
child->SetMleFrameCounter(mleFrameCounter);
|
|
break;
|
|
case kErrorNotFound:
|
|
break;
|
|
default:
|
|
ExitNow(error = kErrorNone);
|
|
}
|
|
|
|
switch (Tlv::Find<TimeoutTlv>(aRxInfo.mMessage, timeout))
|
|
{
|
|
case kErrorNone:
|
|
child->SetTimeout(timeout);
|
|
break;
|
|
case kErrorNotFound:
|
|
break;
|
|
default:
|
|
ExitNow(error = kErrorParse);
|
|
}
|
|
|
|
{
|
|
uint16_t supervisionInterval;
|
|
|
|
switch (Tlv::Find<SupervisionIntervalTlv>(aRxInfo.mMessage, supervisionInterval))
|
|
{
|
|
case kErrorNone:
|
|
child->SetSupervisionInterval(supervisionInterval);
|
|
break;
|
|
case kErrorNotFound:
|
|
break;
|
|
default:
|
|
ExitNow(error = kErrorParse);
|
|
}
|
|
}
|
|
|
|
switch (ProcessAddressRegistrationTlv(aRxInfo, *child))
|
|
{
|
|
case kErrorNone:
|
|
case kErrorNotFound:
|
|
break;
|
|
default:
|
|
ExitNow(error = kErrorParse);
|
|
}
|
|
|
|
switch (aRxInfo.mMessage.ReadLeaderDataTlv(leaderData))
|
|
{
|
|
case kErrorNone:
|
|
child->SetNetworkDataVersion(leaderData.GetDataVersion(child->GetNetworkDataType()));
|
|
break;
|
|
case kErrorNotFound:
|
|
break;
|
|
default:
|
|
ExitNow(error = kErrorParse);
|
|
}
|
|
|
|
SetChildStateToValid(*child);
|
|
child->SetLastHeard(TimerMilli::GetNow());
|
|
child->SetKeySequence(aRxInfo.mKeySequence);
|
|
child->GetLinkInfo().AddRss(aRxInfo.mMessage.GetAverageRss());
|
|
|
|
aRxInfo.mClass = response.IsEmpty() ? RxInfo::kPeerMessage : RxInfo::kAuthoritativeMessage;
|
|
|
|
exit:
|
|
LogProcessError(kTypeChildUpdateResponseOfChild, error);
|
|
}
|
|
|
|
void Mle::HandleDataRequest(RxInfo &aRxInfo)
|
|
{
|
|
Error error = kErrorNone;
|
|
TlvList tlvList;
|
|
MeshCoP::Timestamp timestamp;
|
|
|
|
Log(kMessageReceive, kTypeDataRequest, aRxInfo.mMessageInfo.GetPeerAddr());
|
|
|
|
VerifyOrExit(aRxInfo.IsNeighborStateValid(), error = kErrorSecurity);
|
|
|
|
SuccessOrExit(error = aRxInfo.mMessage.ReadTlvRequestTlv(tlvList));
|
|
|
|
switch (Tlv::Find<ActiveTimestampTlv>(aRxInfo.mMessage, timestamp))
|
|
{
|
|
case kErrorNone:
|
|
if (timestamp == Get<MeshCoP::ActiveDatasetManager>().GetTimestamp())
|
|
{
|
|
break;
|
|
}
|
|
|
|
OT_FALL_THROUGH;
|
|
|
|
case kErrorNotFound:
|
|
tlvList.Add(Tlv::kActiveDataset);
|
|
break;
|
|
|
|
default:
|
|
ExitNow(error = kErrorParse);
|
|
}
|
|
|
|
switch (Tlv::Find<PendingTimestampTlv>(aRxInfo.mMessage, timestamp))
|
|
{
|
|
case kErrorNone:
|
|
if (timestamp == Get<MeshCoP::PendingDatasetManager>().GetTimestamp())
|
|
{
|
|
break;
|
|
}
|
|
|
|
OT_FALL_THROUGH;
|
|
|
|
case kErrorNotFound:
|
|
tlvList.Add(Tlv::kPendingDataset);
|
|
break;
|
|
|
|
default:
|
|
ExitNow(error = kErrorParse);
|
|
}
|
|
|
|
aRxInfo.mClass = RxInfo::kPeerMessage;
|
|
ProcessKeySequence(aRxInfo);
|
|
|
|
SendDataResponse(aRxInfo.mMessageInfo.GetPeerAddr(), tlvList, &aRxInfo.mMessage);
|
|
|
|
exit:
|
|
LogProcessError(kTypeDataRequest, error);
|
|
}
|
|
|
|
void Mle::HandleNetworkDataUpdateRouter(void)
|
|
{
|
|
VerifyOrExit(IsRouterOrLeader());
|
|
|
|
if (IsLeader())
|
|
{
|
|
SendMulticastDataResponse();
|
|
}
|
|
else
|
|
{
|
|
mDelayedSender.ScheduleMulticastDataResponse(GenerateRandomDelay(kUnsolicitedDataResponseJitter));
|
|
}
|
|
|
|
SynchronizeChildNetworkData();
|
|
|
|
exit:
|
|
return;
|
|
}
|
|
|
|
void Mle::SynchronizeChildNetworkData(void)
|
|
{
|
|
VerifyOrExit(IsRouterOrLeader());
|
|
|
|
for (Child &child : Get<ChildTable>().Iterate(Child::kInStateValid))
|
|
{
|
|
if (child.IsRxOnWhenIdle())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (child.GetNetworkDataVersion() == Get<NetworkData::Leader>().GetVersion(child.GetNetworkDataType()))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
SuccessOrExit(SendChildUpdateRequestToChild(child));
|
|
}
|
|
|
|
exit:
|
|
return;
|
|
}
|
|
|
|
#if OPENTHREAD_CONFIG_MLE_STEERING_DATA_SET_OOB_ENABLE
|
|
void Mle::SetSteeringData(const Mac::ExtAddress *aExtAddress)
|
|
{
|
|
Mac::ExtAddress nullExtAddr;
|
|
Mac::ExtAddress allowAnyExtAddr;
|
|
|
|
nullExtAddr.Clear();
|
|
allowAnyExtAddr.Fill(0xff);
|
|
|
|
if ((aExtAddress == nullptr) || (*aExtAddress == nullExtAddr))
|
|
{
|
|
mSteeringData.Clear();
|
|
}
|
|
else if (*aExtAddress == allowAnyExtAddr)
|
|
{
|
|
mSteeringData.SetToPermitAllJoiners();
|
|
}
|
|
else
|
|
{
|
|
Mac::ExtAddress joinerId;
|
|
|
|
IgnoreError(mSteeringData.Init(MeshCoP::SteeringData::kMaxLength));
|
|
MeshCoP::ComputeJoinerId(*aExtAddress, joinerId);
|
|
IgnoreError(mSteeringData.UpdateBloomFilter(joinerId));
|
|
}
|
|
}
|
|
#endif // OPENTHREAD_CONFIG_MLE_STEERING_DATA_SET_OOB_ENABLE
|
|
|
|
void Mle::HandleDiscoveryRequest(RxInfo &aRxInfo)
|
|
{
|
|
Error error = kErrorNone;
|
|
bool parsedDiscoveryRequestTlv = false;
|
|
Tlv::Info tlvInfo;
|
|
MeshCoP::DiscoveryRequestTlvValue discoveryRequestTlvValue;
|
|
MeshCoP::ExtendedPanId extPanId;
|
|
OffsetRange offsetRange;
|
|
DiscoveryResponseInfo responseInfo;
|
|
|
|
Log(kMessageReceive, kTypeDiscoveryRequest, aRxInfo.mMessageInfo.GetPeerAddr());
|
|
|
|
VerifyOrExit(IsRouterRoleAllowed(), error = kErrorInvalidState);
|
|
|
|
SuccessOrExit(error = Tlv::FindTlvValueOffsetRange(aRxInfo.mMessage, Tlv::kDiscovery, offsetRange));
|
|
|
|
for (; !offsetRange.IsEmpty(); offsetRange.AdvanceOffset(tlvInfo.GetSize()))
|
|
{
|
|
SuccessOrExit(error = tlvInfo.ParseFrom(aRxInfo.mMessage, offsetRange));
|
|
|
|
if (tlvInfo.IsExtended())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
switch (tlvInfo.GetType())
|
|
{
|
|
case MeshCoP::Tlv::kDiscoveryRequest:
|
|
SuccessOrExit(error =
|
|
tlvInfo.Read<MeshCoP::DiscoveryRequestTlv>(aRxInfo.mMessage, discoveryRequestTlvValue));
|
|
parsedDiscoveryRequestTlv = true;
|
|
break;
|
|
|
|
case MeshCoP::Tlv::kExtendedPanId:
|
|
SuccessOrExit(error = tlvInfo.Read<MeshCoP::ExtendedPanIdTlv>(aRxInfo.mMessage, extPanId));
|
|
VerifyOrExit(Get<MeshCoP::NetworkIdentity>().GetExtPanId() != extPanId, error = kErrorDrop);
|
|
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (parsedDiscoveryRequestTlv)
|
|
{
|
|
#if OPENTHREAD_CONFIG_MLE_DISCOVERY_SCAN_REQUEST_CALLBACK_ENABLE
|
|
if (mDiscoveryRequestCallback.IsSet())
|
|
{
|
|
DiscoveryRequestInfo info;
|
|
|
|
AsCoreType(&info.mExtAddress).SetFromIid(aRxInfo.mMessageInfo.GetPeerAddr().GetIid());
|
|
info.mVersion = discoveryRequestTlvValue.GetVersion();
|
|
info.mIsJoiner = discoveryRequestTlvValue.GetJoinerFlag();
|
|
|
|
mDiscoveryRequestCallback.Invoke(&info);
|
|
}
|
|
#endif
|
|
|
|
if (discoveryRequestTlvValue.GetJoinerFlag())
|
|
{
|
|
#if OPENTHREAD_CONFIG_MLE_STEERING_DATA_SET_OOB_ENABLE
|
|
if (!mSteeringData.IsEmpty())
|
|
{
|
|
}
|
|
else // if steering data is not set out of band, fall back to network data
|
|
#endif
|
|
{
|
|
VerifyOrExit(Get<NetworkData::Leader>().IsJoiningAllowed(), error = kErrorSecurity);
|
|
}
|
|
}
|
|
}
|
|
|
|
responseInfo.mPanId = aRxInfo.mMessage.GetPanId();
|
|
|
|
#if OPENTHREAD_CONFIG_MULTI_RADIO
|
|
// Send the MLE Discovery Response message on same radio link
|
|
// from which the "MLE Discover Request" message was received.
|
|
responseInfo.mRadioType = aRxInfo.mMessage.GetRadioType();
|
|
#endif
|
|
|
|
mDelayedSender.ScheduleDiscoveryResponse(aRxInfo.mMessageInfo.GetPeerAddr(), responseInfo,
|
|
GenerateRandomDelay(kDiscoveryMaxJitter));
|
|
|
|
exit:
|
|
LogProcessError(kTypeDiscoveryRequest, error);
|
|
}
|
|
|
|
Error Mle::SendDiscoveryResponse(const Ip6::Address &aDestination, const DiscoveryResponseInfo &aInfo)
|
|
{
|
|
Error error = kErrorNone;
|
|
TxMessage *message;
|
|
Tlv::Bookmark tlvBookmark;
|
|
MeshCoP::DiscoveryResponseTlvValue discoveryResponseTlvValue;
|
|
|
|
VerifyOrExit((message = NewMleMessage(kCommandDiscoveryResponse)) != nullptr, error = kErrorNoBufs);
|
|
message->SetDirectTransmission();
|
|
message->SetPanId(aInfo.mPanId);
|
|
#if OPENTHREAD_CONFIG_MULTI_RADIO
|
|
message->SetRadioType(aInfo.mRadioType);
|
|
#endif
|
|
|
|
SuccessOrExit(error = Tlv::StartTlv(*message, Tlv::kDiscovery, tlvBookmark));
|
|
|
|
discoveryResponseTlvValue.Clear();
|
|
discoveryResponseTlvValue.SetVersion(kThreadVersion);
|
|
|
|
#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE
|
|
if (Get<KeyManager>().GetSecurityPolicy().mNativeCommissioningEnabled)
|
|
{
|
|
SuccessOrExit(error = Tlv::Append<MeshCoP::CommissionerUdpPortTlv>(
|
|
*message, Get<MeshCoP::BorderAgent::Manager>().GetUdpPort()));
|
|
|
|
discoveryResponseTlvValue.SetNativeCommissionerFlag();
|
|
}
|
|
#endif
|
|
|
|
if (Get<KeyManager>().GetSecurityPolicy().mCommercialCommissioningEnabled)
|
|
{
|
|
discoveryResponseTlvValue.SetCcmFlag();
|
|
}
|
|
|
|
SuccessOrExit(error = Tlv::Append<MeshCoP::DiscoveryResponseTlv>(*message, discoveryResponseTlvValue));
|
|
|
|
SuccessOrExit(error =
|
|
Tlv::Append<MeshCoP::ExtendedPanIdTlv>(*message, Get<MeshCoP::NetworkIdentity>().GetExtPanId()));
|
|
|
|
SuccessOrExit(error = Tlv::Append<MeshCoP::NetworkNameTlv>(
|
|
*message, Get<MeshCoP::NetworkIdentity>().GetNetworkName().GetAsCString()));
|
|
|
|
SuccessOrExit(error = message->AppendSteeringDataTlv());
|
|
|
|
SuccessOrExit(
|
|
error = Tlv::Append<MeshCoP::JoinerUdpPortTlv>(*message, Get<MeshCoP::JoinerRouter>().GetJoinerUdpPort()));
|
|
|
|
#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_4)
|
|
if (!Get<MeshCoP::NetworkIdentity>().IsDefaultDomainNameSet())
|
|
{
|
|
SuccessOrExit(error = Tlv::Append<MeshCoP::ThreadDomainNameTlv>(
|
|
*message, Get<MeshCoP::NetworkIdentity>().GetDomainName().GetAsCString()));
|
|
}
|
|
#endif
|
|
|
|
SuccessOrExit(error = Tlv::EndTlv(*message, tlvBookmark));
|
|
|
|
SuccessOrExit(error = message->SendTo(aDestination));
|
|
|
|
Log(kMessageSend, kTypeDiscoveryResponse, aDestination);
|
|
|
|
exit:
|
|
FreeMessageOnError(message, error);
|
|
LogProcessError(kTypeDiscoveryResponse, error);
|
|
return error;
|
|
}
|
|
|
|
Error Mle::SendChildIdResponse(Child &aChild)
|
|
{
|
|
Error error = kErrorNone;
|
|
Ip6::Address destination;
|
|
TxMessage *message;
|
|
|
|
VerifyOrExit((message = NewMleMessage(kCommandChildIdResponse)) != nullptr, error = kErrorNoBufs);
|
|
SuccessOrExit(error = message->AppendSourceAddressAndLeaderDataTlvs());
|
|
SuccessOrExit(error = message->AppendActiveAndPendingTimestampTlvs());
|
|
|
|
if ((aChild.GetRloc16() == 0) || !HasMatchingRouterIdWith(aChild.GetRloc16()))
|
|
{
|
|
aChild.SetRloc16(mChildTable.AllocateNewChildRloc16());
|
|
}
|
|
|
|
SuccessOrExit(error = message->AppendAddress16Tlv(aChild.GetRloc16()));
|
|
|
|
for (uint8_t i = 0; i < Child::kMaxRequestTlvs; i++)
|
|
{
|
|
switch (aChild.GetRequestTlv(i))
|
|
{
|
|
case Tlv::kNetworkData:
|
|
SuccessOrExit(error = message->AppendNetworkDataTlv(aChild.GetNetworkDataType()));
|
|
break;
|
|
|
|
case Tlv::kRoute:
|
|
SuccessOrExit(error = message->AppendRouteTlv());
|
|
break;
|
|
|
|
case Tlv::kActiveDataset:
|
|
SuccessOrExit(error = message->AppendActiveDatasetTlv());
|
|
break;
|
|
|
|
case Tlv::kPendingDataset:
|
|
SuccessOrExit(error = message->AppendPendingDatasetTlv());
|
|
break;
|
|
|
|
case Tlv::kSupervisionInterval:
|
|
SuccessOrExit(error = message->AppendSupervisionIntervalTlv(aChild.GetSupervisionInterval()));
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!aChild.IsFullThreadDevice())
|
|
{
|
|
SuccessOrExit(error = message->AppendAddressRegistrationTlv(aChild));
|
|
}
|
|
|
|
if (!aChild.IsRxOnWhenIdle())
|
|
{
|
|
Get<IndirectSender>().SetChildUseShortAddress(aChild, false);
|
|
}
|
|
|
|
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
|
|
if (aChild.IsTimeSyncEnabled())
|
|
{
|
|
message->SetTimeSync(true);
|
|
}
|
|
#endif
|
|
|
|
destination.InitAsLinkLocalAddress(aChild.GetExtAddress());
|
|
SuccessOrExit(error = message->SendTo(destination));
|
|
|
|
SetChildStateToValid(aChild);
|
|
|
|
Log(kMessageSend, kTypeChildIdResponse, destination, aChild.GetRloc16());
|
|
|
|
exit:
|
|
FreeMessageOnError(message, error);
|
|
return error;
|
|
}
|
|
|
|
Error Mle::SendChildUpdateRequestToChild(Child &aChild)
|
|
{
|
|
static const uint8_t kTlvs[] = {Tlv::kTimeout, Tlv::kAddressRegistration};
|
|
|
|
Error error = kErrorNone;
|
|
Ip6::Address destination;
|
|
TxMessage *message = nullptr;
|
|
|
|
if (!aChild.IsRxOnWhenIdle() && aChild.IsStateRestoring())
|
|
{
|
|
// No need to send the resync "Child Update Request"
|
|
// to the sleepy child if there is one already
|
|
// queued.
|
|
|
|
VerifyOrExit(!Get<IndirectSender>().HasQueuedMessageForSleepyChild(aChild, IsMessageChildUpdateRequest));
|
|
}
|
|
|
|
Get<MeshForwarder>().RemoveMessagesForChild(aChild, IsMessageChildUpdateRequest);
|
|
|
|
VerifyOrExit((message = NewMleMessage(kCommandChildUpdateRequest)) != nullptr, error = kErrorNoBufs);
|
|
SuccessOrExit(error = message->AppendSourceAddressAndLeaderDataTlvs());
|
|
SuccessOrExit(error = message->AppendNetworkDataTlv(aChild.GetNetworkDataType()));
|
|
SuccessOrExit(error = message->AppendActiveAndPendingTimestampTlvs());
|
|
|
|
if (aChild.IsStateValid())
|
|
{
|
|
SuccessOrExit(error = message->AppendLinkMarginTlv(aChild.GetLinkInfo().GetLinkMargin()));
|
|
}
|
|
else
|
|
{
|
|
SuccessOrExit(error = message->AppendTlvRequestTlv(kTlvs));
|
|
|
|
if (!aChild.IsStateRestored())
|
|
{
|
|
// A random challenge is generated and saved when `aChild`
|
|
// is first initialized in `kStateRestored`. We will use
|
|
// the saved challenge here. This prevents overwriting
|
|
// the saved challenge when the child is also detached
|
|
// and happens to send a "Parent Request" in the window
|
|
// where the parent transitions to the router/leader role
|
|
// and before the parent sends the "Child Update Request".
|
|
// This ensures that the same random challenge is
|
|
// included in both "Parent Response" and "Child Update
|
|
// Response," guaranteeing proper acceptance of the
|
|
// child's "Child ID request".
|
|
|
|
aChild.GenerateChallenge();
|
|
}
|
|
|
|
SuccessOrExit(error = message->AppendChallengeTlv(aChild.GetChallenge()));
|
|
}
|
|
|
|
destination.InitAsLinkLocalAddress(aChild.GetExtAddress());
|
|
SuccessOrExit(error = message->SendTo(destination));
|
|
|
|
if (aChild.IsRxOnWhenIdle())
|
|
{
|
|
// only try to send a single Child Update Request message to an rx-on-when-idle child
|
|
aChild.SetState(Child::kStateChildUpdateRequest);
|
|
}
|
|
|
|
Log(kMessageSend, kTypeChildUpdateRequestOfChild, destination, aChild.GetRloc16());
|
|
|
|
exit:
|
|
FreeMessageOnError(message, error);
|
|
return error;
|
|
}
|
|
|
|
void Mle::SendChildUpdateResponseToChild(Child *aChild, const ChildUpdateResponseInfo &aInfo)
|
|
{
|
|
Error error = kErrorNone;
|
|
TxMessage *message;
|
|
|
|
VerifyOrExit((message = NewMleMessage(kCommandChildUpdateResponse)) != nullptr, error = kErrorNoBufs);
|
|
|
|
for (uint8_t tlvType : aInfo.mTlvList)
|
|
{
|
|
// Add all TLV types that do not depend on `child`
|
|
|
|
switch (tlvType)
|
|
{
|
|
case Tlv::kStatus:
|
|
SuccessOrExit(error = message->AppendStatusTlv(kStatusError));
|
|
break;
|
|
|
|
case Tlv::kLeaderData:
|
|
SuccessOrExit(error = message->AppendLeaderDataTlv());
|
|
break;
|
|
|
|
case Tlv::kResponse:
|
|
SuccessOrExit(error = message->AppendResponseTlv(aInfo.mChallenge));
|
|
break;
|
|
|
|
case Tlv::kSourceAddress:
|
|
SuccessOrExit(error = message->AppendSourceAddressTlv());
|
|
break;
|
|
|
|
case Tlv::kMleFrameCounter:
|
|
SuccessOrExit(error = message->AppendMleFrameCounterTlv());
|
|
break;
|
|
|
|
case Tlv::kLinkFrameCounter:
|
|
SuccessOrExit(error = message->AppendLinkFrameCounterTlv());
|
|
break;
|
|
}
|
|
|
|
// Make sure `child` is not null before adding TLV types
|
|
// that can depend on it.
|
|
|
|
if (aChild == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
switch (tlvType)
|
|
{
|
|
case Tlv::kAddressRegistration:
|
|
SuccessOrExit(error = message->AppendAddressRegistrationTlv(*aChild));
|
|
break;
|
|
|
|
case Tlv::kMode:
|
|
SuccessOrExit(error = message->AppendModeTlv(aChild->GetDeviceMode()));
|
|
break;
|
|
|
|
case Tlv::kNetworkData:
|
|
SuccessOrExit(error = message->AppendNetworkDataTlv(aChild->GetNetworkDataType()));
|
|
SuccessOrExit(error = message->AppendActiveAndPendingTimestampTlvs());
|
|
break;
|
|
|
|
case Tlv::kTimeout:
|
|
SuccessOrExit(error = message->AppendTimeoutTlv(aChild->GetTimeout()));
|
|
break;
|
|
|
|
case Tlv::kLinkMargin:
|
|
SuccessOrExit(error = message->AppendLinkMarginTlv(aChild->GetLinkInfo().GetLinkMargin()));
|
|
break;
|
|
|
|
case Tlv::kSupervisionInterval:
|
|
SuccessOrExit(error = message->AppendSupervisionIntervalTlv(aChild->GetSupervisionInterval()));
|
|
break;
|
|
|
|
#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
|
|
case Tlv::kCslClockAccuracy:
|
|
if (!aChild->IsRxOnWhenIdle())
|
|
{
|
|
SuccessOrExit(error = message->AppendCslClockAccuracyTlv());
|
|
}
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
SuccessOrExit(error = message->SendTo(aInfo.mDestination));
|
|
|
|
if (aChild == nullptr)
|
|
{
|
|
Log(kMessageSend, kTypeChildUpdateResponseOfChild, aInfo.mDestination);
|
|
}
|
|
else
|
|
{
|
|
Log(kMessageSend, kTypeChildUpdateResponseOfChild, aInfo.mDestination, aChild->GetRloc16());
|
|
}
|
|
|
|
exit:
|
|
FreeMessageOnError(message, error);
|
|
}
|
|
|
|
void Mle::SendMulticastDataResponse(void)
|
|
{
|
|
TlvList tlvList;
|
|
|
|
tlvList.Add(Tlv::kNetworkData);
|
|
SendDataResponse(Ip6::Address::GetLinkLocalAllNodesMulticast(), tlvList);
|
|
}
|
|
|
|
void Mle::SendDataResponse(const Ip6::Address &aDestination, const TlvList &aTlvList, const Message *aRequestMessage)
|
|
{
|
|
OT_UNUSED_VARIABLE(aRequestMessage);
|
|
|
|
Error error = kErrorNone;
|
|
TxMessage *message = nullptr;
|
|
Neighbor *neighbor;
|
|
|
|
VerifyOrExit(IsAttached());
|
|
|
|
if (mRetrieveNewNetworkData)
|
|
{
|
|
LogInfo("Suppressing Data Response - waiting for new network data");
|
|
ExitNow();
|
|
}
|
|
|
|
VerifyOrExit((message = NewMleMessage(kCommandDataResponse)) != nullptr, error = kErrorNoBufs);
|
|
SuccessOrExit(error = message->AppendSourceAddressAndLeaderDataTlvs());
|
|
SuccessOrExit(error = message->AppendActiveAndPendingTimestampTlvs());
|
|
|
|
for (uint8_t tlvType : aTlvList)
|
|
{
|
|
switch (tlvType)
|
|
{
|
|
case Tlv::kNetworkData:
|
|
neighbor = mNeighborTable.FindNeighbor(aDestination);
|
|
SuccessOrExit(error = message->AppendNetworkDataTlv((neighbor != nullptr) ? neighbor->GetNetworkDataType()
|
|
: NetworkData::kFullSet));
|
|
break;
|
|
|
|
case Tlv::kActiveDataset:
|
|
SuccessOrExit(error = message->AppendActiveDatasetTlv());
|
|
break;
|
|
|
|
case Tlv::kPendingDataset:
|
|
SuccessOrExit(error = message->AppendPendingDatasetTlv());
|
|
break;
|
|
|
|
case Tlv::kRoute:
|
|
SuccessOrExit(error = message->AppendRouteTlv());
|
|
break;
|
|
|
|
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
|
|
case Tlv::kLinkMetricsReport:
|
|
OT_ASSERT(aRequestMessage != nullptr);
|
|
neighbor = mNeighborTable.FindNeighbor(aDestination);
|
|
VerifyOrExit(neighbor != nullptr, error = kErrorInvalidState);
|
|
SuccessOrExit(error = Get<LinkMetrics::Subject>().AppendReport(*message, *aRequestMessage, *neighbor));
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
SuccessOrExit(error = message->SendTo(aDestination));
|
|
Log(kMessageSend, kTypeDataResponse, aDestination);
|
|
|
|
exit:
|
|
FreeMessageOnError(message, error);
|
|
LogSendError(kTypeDataResponse, error);
|
|
}
|
|
|
|
void Mle::RemoveRouterLink(Router &aRouter)
|
|
{
|
|
switch (mRole)
|
|
{
|
|
case kRoleChild:
|
|
if (&aRouter == &mParent)
|
|
{
|
|
IgnoreError(BecomeDetached());
|
|
}
|
|
break;
|
|
|
|
case kRoleRouter:
|
|
case kRoleLeader:
|
|
mRouterTable.RemoveRouterLink(aRouter);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Mle::RemoveNeighbor(Neighbor &aNeighbor)
|
|
{
|
|
VerifyOrExit(!aNeighbor.IsStateInvalid());
|
|
|
|
if (&aNeighbor == &mParent)
|
|
{
|
|
if (IsChild())
|
|
{
|
|
IgnoreError(BecomeDetached());
|
|
}
|
|
}
|
|
else if (&aNeighbor == &GetParentCandidate())
|
|
{
|
|
mAttacher.ClearParentCandidate();
|
|
}
|
|
else if (IsChildRloc16(aNeighbor.GetRloc16()))
|
|
{
|
|
OT_ASSERT(mChildTable.Contains(aNeighbor));
|
|
|
|
if (aNeighbor.IsStateValidOrRestoring())
|
|
{
|
|
mNeighborTable.Signal(NeighborTable::kChildRemoved, aNeighbor);
|
|
}
|
|
|
|
Get<IndirectSender>().ClearAllMessagesForSleepyChild(static_cast<Child &>(aNeighbor));
|
|
|
|
if (aNeighbor.IsFullThreadDevice())
|
|
{
|
|
Get<AddressResolver>().RemoveEntriesForRloc16(aNeighbor.GetRloc16());
|
|
}
|
|
|
|
mChildTable.RemoveStoredChild(static_cast<Child &>(aNeighbor));
|
|
}
|
|
else if (aNeighbor.IsStateValid())
|
|
{
|
|
OT_ASSERT(mRouterTable.Contains(aNeighbor));
|
|
|
|
mNeighborTable.Signal(NeighborTable::kRouterRemoved, aNeighbor);
|
|
mRouterTable.RemoveRouterLink(static_cast<Router &>(aNeighbor));
|
|
}
|
|
|
|
aNeighbor.GetLinkInfo().Clear();
|
|
aNeighbor.SetState(Neighbor::kStateInvalid);
|
|
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
|
|
aNeighbor.RemoveAllForwardTrackingSeriesInfo();
|
|
#endif
|
|
|
|
exit:
|
|
return;
|
|
}
|
|
|
|
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
|
|
Error Mle::SetPreferredRouterId(uint8_t aRouterId)
|
|
{
|
|
Error error = kErrorNone;
|
|
|
|
VerifyOrExit(IsDetached() || IsDisabled(), error = kErrorInvalidState);
|
|
|
|
mPreviousRouterId = aRouterId;
|
|
|
|
exit:
|
|
return error;
|
|
}
|
|
#endif
|
|
|
|
void Mle::SetRouterId(uint8_t aRouterId)
|
|
{
|
|
mRouterId = aRouterId;
|
|
mPreviousRouterId = mRouterId;
|
|
}
|
|
|
|
Error Mle::SendAddressSolicit(RouterUpgradeReason aReason)
|
|
{
|
|
Error error = kErrorNone;
|
|
Coap::Message *message = nullptr;
|
|
Ip6::Address leaderRloc;
|
|
|
|
VerifyOrExit(!mAddressSolicitPending);
|
|
|
|
message = Get<Tmf::Agent>().AllocateAndInitPriorityConfirmablePostMessage(kUriAddressSolicit);
|
|
VerifyOrExit(message != nullptr, error = kErrorNoBufs);
|
|
|
|
SuccessOrExit(error = Tlv::Append<ThreadExtMacAddressTlv>(*message, Get<Mac::Mac>().GetExtAddress()));
|
|
|
|
if (IsRouterIdValid(mPreviousRouterId))
|
|
{
|
|
SuccessOrExit(error = Tlv::Append<ThreadRloc16Tlv>(*message, Rloc16FromRouterId(mPreviousRouterId)));
|
|
}
|
|
|
|
SuccessOrExit(error = Tlv::Append<ThreadStatusTlv>(*message, aReason));
|
|
|
|
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
|
|
SuccessOrExit(error = Tlv::Append<XtalAccuracyTlv>(*message, otPlatTimeGetXtalAccuracy()));
|
|
#endif
|
|
|
|
GetLeaderRloc(leaderRloc);
|
|
|
|
SuccessOrExit(error = Get<Tmf::Agent>().SendMessageTo(*message, leaderRloc, HandleAddressSolicitResponse, this));
|
|
mAddressSolicitPending = true;
|
|
|
|
Log(kMessageSend, kTypeAddressSolicit, leaderRloc);
|
|
|
|
exit:
|
|
FreeMessageOnError(message, error);
|
|
return error;
|
|
}
|
|
|
|
void Mle::SendAddressRelease(void)
|
|
{
|
|
Error error = kErrorNone;
|
|
Coap::Message *message;
|
|
Ip6::Address leaderRloc;
|
|
|
|
message = Get<Tmf::Agent>().AllocateAndInitPriorityConfirmablePostMessage(kUriAddressRelease);
|
|
VerifyOrExit(message != nullptr, error = kErrorNoBufs);
|
|
|
|
SuccessOrExit(error = Tlv::Append<ThreadRloc16Tlv>(*message, Rloc16FromRouterId(mRouterId)));
|
|
SuccessOrExit(error = Tlv::Append<ThreadExtMacAddressTlv>(*message, Get<Mac::Mac>().GetExtAddress()));
|
|
|
|
GetLeaderRloc(leaderRloc);
|
|
|
|
SuccessOrExit(error = Get<Tmf::Agent>().SendMessageTo(*message, leaderRloc));
|
|
|
|
Log(kMessageSend, kTypeAddressRelease, leaderRloc);
|
|
|
|
exit:
|
|
FreeMessageOnError(message, error);
|
|
LogSendError(kTypeAddressRelease, error);
|
|
}
|
|
|
|
void Mle::HandleAddressSolicitResponse(Coap::Msg *aMsg, Error aResult)
|
|
{
|
|
uint8_t status;
|
|
uint16_t rloc16;
|
|
RouterIdMask routerIdMask;
|
|
uint8_t routerId;
|
|
Router *router;
|
|
|
|
mAddressSolicitPending = false;
|
|
|
|
VerifyOrExit(aResult == kErrorNone && aMsg != nullptr);
|
|
|
|
VerifyOrExit(aMsg->GetCode() == Coap::kCodeChanged);
|
|
|
|
Log(kMessageReceive, kTypeAddressReply, aMsg->mMessageInfo.GetPeerAddr());
|
|
|
|
SuccessOrExit(Tlv::Find<ThreadStatusTlv>(aMsg->mMessage, status));
|
|
|
|
if (status != kAddrSolicitSuccess)
|
|
{
|
|
mAddressSolicitRejected = true;
|
|
|
|
if (IsRouterIdValid(mPreviousRouterId))
|
|
{
|
|
if (HasChildren())
|
|
{
|
|
RemoveChildren();
|
|
}
|
|
|
|
SetRouterId(kInvalidRouterId);
|
|
}
|
|
|
|
ExitNow();
|
|
}
|
|
|
|
SuccessOrExit(Tlv::Find<ThreadRloc16Tlv>(aMsg->mMessage, rloc16));
|
|
routerId = RouterIdFromRloc16(rloc16);
|
|
|
|
SuccessOrExit(Tlv::Find<ThreadRouterMaskTlv>(aMsg->mMessage, routerIdMask));
|
|
VerifyOrExit(routerIdMask.IsValid());
|
|
VerifyOrExit(routerIdMask.IsAllocated(GetLeaderId()));
|
|
|
|
SetAlternateRloc16(GetRloc16());
|
|
|
|
SetRouterId(routerId);
|
|
|
|
SetStateRouter(Rloc16FromRouterId(mRouterId));
|
|
|
|
// We keep the router table next hop and cost as what we had as a
|
|
// REED, i.e., our parent was the next hop towards all other
|
|
// routers and we tracked its cost towards them. As an FTD child,
|
|
// we may have established links with a subset of neighboring routers.
|
|
// We ensure to clear these links to avoid using them (since will
|
|
// be rejected by the neighbor).
|
|
|
|
mRouterTable.ClearNeighbors();
|
|
|
|
mRouterTable.UpdateRouterIdMask(routerIdMask);
|
|
|
|
router = mRouterTable.FindRouterById(routerId);
|
|
VerifyOrExit(router != nullptr);
|
|
router->SetExtAddress(Get<Mac::Mac>().GetExtAddress());
|
|
router->SetNextHopToInvalid();
|
|
|
|
// Ensure we have our parent as a neighboring router, copying the
|
|
// `mParent` entry.
|
|
|
|
router = mRouterTable.FindRouterById(mParent.GetRouterId());
|
|
VerifyOrExit(router != nullptr);
|
|
router->SetFrom(mParent);
|
|
router->SetState(Neighbor::kStateValid);
|
|
router->SetNextHopToInvalid();
|
|
|
|
// Ensure we have a next hop and cost towards leader.
|
|
if (mRouterTable.GetPathCostToLeader() >= kMaxRouteCost)
|
|
{
|
|
Router *leader = mRouterTable.GetLeader();
|
|
|
|
OT_ASSERT(leader != nullptr);
|
|
leader->SetNextHopAndCost(RouterIdFromRloc16(mParent.GetRloc16()), mParent.GetLeaderCost());
|
|
}
|
|
|
|
// We send a unicast Link Request to our former parent if its
|
|
// version is earlier than 1.3. This is to address a potential
|
|
// compatibility issue with some non-OpenThread stacks which may
|
|
// ignore MLE Advertisements from a former/existing child.
|
|
|
|
if (mParent.GetVersion() < kThreadVersion1p3)
|
|
{
|
|
SendLinkRequest(&mParent);
|
|
}
|
|
|
|
// We send an Advertisement to inform our former parent of our
|
|
// newly allocated Router ID. This will cause the parent to
|
|
// reset its advertisement trickle timer which can help speed
|
|
// up the dissemination of the new Router ID to other routers.
|
|
// This can also help with quicker link establishment with our
|
|
// former parent and other routers.
|
|
SendMulticastAdvertisement();
|
|
|
|
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 `DowngradeBlocked` 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);
|
|
mRoleTransitioner.SetDowngradeBlocked(true);
|
|
}
|
|
|
|
exit:
|
|
mAnnounceHandler.HandleRouterRoleTransitionAttemptDone();
|
|
}
|
|
|
|
Error Mle::SetChildRouterLinks(uint8_t aChildRouterLinks)
|
|
{
|
|
Error error = kErrorNone;
|
|
|
|
VerifyOrExit(IsDisabled(), error = kErrorInvalidState);
|
|
mChildRouterLinks = aChildRouterLinks;
|
|
exit:
|
|
return error;
|
|
}
|
|
|
|
bool Mle::RoleTransitioner::WillBecomeRouterSoon(void) const
|
|
{
|
|
static constexpr uint8_t kMaxDelay = 10;
|
|
|
|
bool willBecomeRouter = false;
|
|
|
|
VerifyOrExit(IsRouterRoleAllowed() && Get<Mle>().IsChild());
|
|
VerifyOrExit(!Get<Mle>().mAddressSolicitRejected);
|
|
|
|
if (!Get<Mle>().mAddressSolicitPending)
|
|
{
|
|
VerifyOrExit(IsTransitionPending() && GetTimeout() <= kMaxDelay);
|
|
}
|
|
|
|
willBecomeRouter = true;
|
|
|
|
exit:
|
|
return willBecomeRouter;
|
|
}
|
|
|
|
Error Mle::AddrSolicitInfo::ParseFrom(const Coap::Msg &aMsg)
|
|
{
|
|
// Parses a `kUriAddressSolicit` request message.
|
|
|
|
Error error;
|
|
|
|
VerifyOrExit(aMsg.IsConfirmable(), error = kErrorParse);
|
|
|
|
SuccessOrExit(error = Tlv::Find<ThreadExtMacAddressTlv>(aMsg.mMessage, mExtAddress));
|
|
SuccessOrExit(error = Tlv::Find<ThreadStatusTlv>(aMsg.mMessage, mReason));
|
|
|
|
switch (Tlv::Find<ThreadRloc16Tlv>(aMsg.mMessage, mRequestedRloc16))
|
|
{
|
|
case kErrorNone:
|
|
break;
|
|
case kErrorNotFound:
|
|
mRequestedRloc16 = kInvalidRloc16;
|
|
break;
|
|
default:
|
|
ExitNow(error = kErrorParse);
|
|
}
|
|
|
|
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
|
|
switch (Tlv::Find<XtalAccuracyTlv>(aMsg.mMessage, mXtalAccuracy))
|
|
{
|
|
case kErrorNone:
|
|
break;
|
|
case kErrorNotFound:
|
|
SetToUintMax(mXtalAccuracy);
|
|
break;
|
|
default:
|
|
ExitNow(error = kErrorParse);
|
|
}
|
|
#endif
|
|
|
|
exit:
|
|
return error;
|
|
}
|
|
|
|
void Mle::ProcessAddressSolicit(AddrSolicitInfo &aInfo)
|
|
{
|
|
// Processes a previously parsed Address Solicit request and
|
|
// determines whether to accept or reject the request.
|
|
|
|
aInfo.mResponse = kAddrSolicitNoAddressAvailable;
|
|
aInfo.mRouter = nullptr;
|
|
|
|
LogInfo("AddrSolicit Reason: %s", RouterUpgradeReasonToString(aInfo.mReason));
|
|
|
|
if (aInfo.mRequestedRloc16 != kInvalidRloc16)
|
|
{
|
|
LogInfo("AddrSolicit Requested ID: %u", RouterIdFromRloc16(aInfo.mRequestedRloc16));
|
|
}
|
|
|
|
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
|
|
VerifyOrExit(aInfo.mXtalAccuracy <= Get<TimeSync>().GetXtalThreshold());
|
|
#endif
|
|
|
|
aInfo.mRouter = mRouterTable.FindRouter(aInfo.mExtAddress);
|
|
|
|
if (aInfo.mRouter != nullptr)
|
|
{
|
|
aInfo.mResponse = kAddrSolicitSuccess;
|
|
ExitNow();
|
|
}
|
|
|
|
switch (aInfo.mReason)
|
|
{
|
|
case kReasonTooFewRouters:
|
|
VerifyOrExit(mRoleTransitioner.IsRouterCountBelowUpgradeThreshold());
|
|
break;
|
|
|
|
case kReasonHaveChildIdRequest:
|
|
case kReasonParentPartitionChange:
|
|
break;
|
|
|
|
case kReasonBorderRouterRequest:
|
|
if (!mRoleTransitioner.IsRouterCountBelowUpgradeThreshold() &&
|
|
(Get<NetworkData::Leader>().CountBorderRouters(NetworkData::kRouterRoleOnly) >=
|
|
kRouterUpgradeBorderRouterRequestThreshold))
|
|
{
|
|
ExitNow();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
aInfo.mResponse = kAddrSolicitUnrecognizedReason;
|
|
ExitNow();
|
|
}
|
|
|
|
if (aInfo.mRequestedRloc16 != kInvalidRloc16)
|
|
{
|
|
aInfo.mRouter = mRouterTable.Allocate(RouterIdFromRloc16(aInfo.mRequestedRloc16));
|
|
}
|
|
|
|
if (aInfo.mRouter == nullptr)
|
|
{
|
|
aInfo.mRouter = mRouterTable.Allocate();
|
|
VerifyOrExit(aInfo.mRouter != nullptr);
|
|
}
|
|
|
|
aInfo.mRouter->SetExtAddress(aInfo.mExtAddress);
|
|
aInfo.mResponse = kAddrSolicitSuccess;
|
|
|
|
exit:
|
|
return;
|
|
}
|
|
|
|
template <> void Mle::HandleTmf<kUriAddressSolicit>(Coap::Msg &aMsg)
|
|
{
|
|
Coap::Message *response = nullptr;
|
|
AddrSolicitInfo info;
|
|
|
|
VerifyOrExit(IsLeader() && !IsAttaching());
|
|
|
|
Log(kMessageReceive, kTypeAddressSolicit, aMsg.mMessageInfo.GetPeerAddr());
|
|
|
|
SuccessOrExit(info.ParseFrom(aMsg));
|
|
|
|
ProcessAddressSolicit(info);
|
|
|
|
// Prepare and send response
|
|
|
|
response = Get<Tmf::Agent>().AllocateAndInitPriorityResponseFor(aMsg.mMessage);
|
|
VerifyOrExit(response != nullptr);
|
|
|
|
SuccessOrExit(Tlv::Append<ThreadStatusTlv>(*response, info.mResponse));
|
|
|
|
if (info.mRouter != nullptr)
|
|
{
|
|
RouterIdMask routerIdMask;
|
|
|
|
SuccessOrExit(Tlv::Append<ThreadRloc16Tlv>(*response, info.mRouter->GetRloc16()));
|
|
|
|
mRouterTable.GetRouterIdMask(routerIdMask);
|
|
SuccessOrExit(Tlv::Append<ThreadRouterMaskTlv>(*response, routerIdMask));
|
|
}
|
|
|
|
SuccessOrExit(Get<Tmf::Agent>().SendMessage(*response, aMsg.mMessageInfo));
|
|
response = nullptr;
|
|
|
|
Log(kMessageSend, kTypeAddressReply, aMsg.mMessageInfo.GetPeerAddr());
|
|
|
|
// If assigning a new RLOC16 (e.g., on promotion of a child to
|
|
// router role) we clear any address cache entries associated
|
|
// with the old RLOC16 unless the sender is a direct child. For
|
|
// direct children, we retain the cache entries to allow
|
|
// association with the promoted router's new RLOC16 upon
|
|
// receiving its Link Advertisement.
|
|
|
|
if ((info.mResponse == kAddrSolicitSuccess) && (info.mRouter != nullptr))
|
|
{
|
|
uint16_t oldRloc16;
|
|
|
|
VerifyOrExit(IsRoutingLocator(aMsg.mMessageInfo.GetPeerAddr()));
|
|
oldRloc16 = aMsg.mMessageInfo.GetPeerAddr().GetIid().GetLocator();
|
|
|
|
VerifyOrExit(oldRloc16 != info.mRouter->GetRloc16());
|
|
VerifyOrExit(!RouterIdMatch(oldRloc16, GetRloc16()));
|
|
Get<AddressResolver>().RemoveEntriesForRloc16(oldRloc16);
|
|
}
|
|
|
|
exit:
|
|
FreeMessage(response);
|
|
}
|
|
|
|
template <> void Mle::HandleTmf<kUriAddressRelease>(Coap::Msg &aMsg)
|
|
{
|
|
uint16_t rloc16;
|
|
Mac::ExtAddress extAddress;
|
|
uint8_t routerId;
|
|
Router *router;
|
|
|
|
VerifyOrExit(mRole == kRoleLeader);
|
|
|
|
VerifyOrExit(aMsg.IsConfirmable());
|
|
|
|
Log(kMessageReceive, kTypeAddressRelease, aMsg.mMessageInfo.GetPeerAddr());
|
|
|
|
SuccessOrExit(Tlv::Find<ThreadRloc16Tlv>(aMsg.mMessage, rloc16));
|
|
SuccessOrExit(Tlv::Find<ThreadExtMacAddressTlv>(aMsg.mMessage, extAddress));
|
|
|
|
routerId = RouterIdFromRloc16(rloc16);
|
|
router = mRouterTable.FindRouterById(routerId);
|
|
|
|
VerifyOrExit((router != nullptr) && (router->GetExtAddress() == extAddress));
|
|
|
|
IgnoreError(mRouterTable.Release(routerId));
|
|
|
|
SuccessOrExit(Get<Tmf::Agent>().SendAckResponse(aMsg));
|
|
|
|
Log(kMessageSend, kTypeAddressReleaseReply, aMsg.mMessageInfo.GetPeerAddr());
|
|
|
|
exit:
|
|
return;
|
|
}
|
|
|
|
void Mle::DetermineConnectivity(Connectivity &aConnectivity) const
|
|
{
|
|
aConnectivity.Clear();
|
|
|
|
aConnectivity.mParentPriority = GetAssignParentPriority();
|
|
|
|
if (aConnectivity.mParentPriority == kParentPriorityUnspecified)
|
|
{
|
|
uint16_t numChildren = mChildTable.GetNumChildren(Child::kInStateValid);
|
|
uint16_t maxAllowed = mChildTable.GetMaxChildrenAllowed();
|
|
|
|
if ((maxAllowed - numChildren) < (maxAllowed / 3))
|
|
{
|
|
aConnectivity.mParentPriority = kParentPriorityLow;
|
|
}
|
|
else
|
|
{
|
|
aConnectivity.mParentPriority = kParentPriorityMedium;
|
|
}
|
|
}
|
|
|
|
if (IsChild())
|
|
{
|
|
aConnectivity.IncrementNumForLinkQuality(mParent.GetLinkQualityIn());
|
|
}
|
|
|
|
for (const Router &router : Get<RouterTable>())
|
|
{
|
|
if (router.GetRloc16() == GetRloc16())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!router.IsStateValid())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
aConnectivity.IncrementNumForLinkQuality(router.GetTwoWayLinkQuality());
|
|
}
|
|
|
|
aConnectivity.mLeaderCost = Min(mRouterTable.GetPathCostToLeader(), kMaxRouteCost);
|
|
aConnectivity.mIdSequence = mRouterTable.GetRouterIdSequence();
|
|
aConnectivity.mActiveRouters = mRouterTable.GetActiveRouterCount();
|
|
aConnectivity.mSedBufferSize = Connectivity::kDefaultSedBufferSize;
|
|
aConnectivity.mSedDatagramCount = Connectivity::kDefaultSedDatagramCount;
|
|
}
|
|
|
|
void Mle::FillConnectivityTlvValue(ConnectivityTlvValue &aTlvValue) const
|
|
{
|
|
Connectivity connectivity;
|
|
|
|
DetermineConnectivity(connectivity);
|
|
aTlvValue.InitFrom(connectivity);
|
|
}
|
|
|
|
void Mle::RoleTransitioner::DecideWhetherToDowngrade(uint8_t aNeighborId, const RouteTlv::Data &aRouteTlvData)
|
|
{
|
|
// Determine whether all conditions are satisfied for the router
|
|
// to downgrade after receiving info for a neighboring router
|
|
// with Router ID `aNeighborId` along with its `aRouteTlvData`.
|
|
|
|
uint8_t activeRouterCount = Get<RouterTable>().GetActiveRouterCount();
|
|
uint8_t count;
|
|
|
|
VerifyOrExit(Get<Mle>().IsRouter());
|
|
VerifyOrExit(Get<RouterTable>().IsAllocated(aNeighborId));
|
|
VerifyOrExit(!mDowngradeBlocked);
|
|
|
|
VerifyOrExit(!IsTransitionPending());
|
|
|
|
VerifyOrExit(activeRouterCount > mDowngradeThreshold);
|
|
|
|
// Check that we have at least `kMinDowngradeNeighbors`
|
|
// neighboring routers with two-way link quality of 2 or better.
|
|
|
|
count = 0;
|
|
|
|
for (const Router &router : Get<RouterTable>())
|
|
{
|
|
if (!router.IsStateValid() || (router.GetTwoWayLinkQuality() < kLinkQuality2))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
count++;
|
|
|
|
if (count >= kMinDowngradeNeighbors)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
VerifyOrExit(count >= kMinDowngradeNeighbors);
|
|
|
|
// Check that we have fewer children than three times the number
|
|
// of excess routers (defined as the difference between number of
|
|
// active routers and `mDowngradeThreshold`).
|
|
|
|
count = activeRouterCount - mDowngradeThreshold;
|
|
VerifyOrExit(Get<ChildTable>().GetNumChildren(Child::kInStateValid) < count * 3);
|
|
|
|
// Check that the neighbor has as good or better-quality links to
|
|
// same routers.
|
|
|
|
VerifyOrExit(NeighborHasComparableConnectivity(aNeighborId, aRouteTlvData));
|
|
|
|
#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE && OPENTHREAD_CONFIG_BORDER_ROUTER_REQUEST_ROUTER_ROLE
|
|
// Check if we are eligible to be router due to being a BR.
|
|
VerifyOrExit(!Get<NetworkData::Notifier>().IsEligibleForRouterRoleUpgradeAsBorderRouter());
|
|
#endif
|
|
|
|
// All conditions are satisfied to start downgrade.
|
|
StartTimeout();
|
|
|
|
exit:
|
|
return;
|
|
}
|
|
|
|
bool Mle::RoleTransitioner::NeighborHasComparableConnectivity(uint8_t aNeighborId,
|
|
const RouteTlv::Data &aRouteTlvData) const
|
|
{
|
|
// Check whether the neighboring router with Router ID `aNeighborId`
|
|
// (along with its `aRouteTlvData`) has as good or better-quality
|
|
// links to all our neighboring routers which have a two-way link
|
|
// quality of two or better.
|
|
|
|
bool isComparable = true;
|
|
|
|
for (uint8_t routerId = 0; routerId <= kMaxRouterId; routerId++)
|
|
{
|
|
const Router *router;
|
|
LinkQuality localLinkQuality;
|
|
LinkQuality peerLinkQuality;
|
|
const RouteTlv::Data::Entry *entry;
|
|
|
|
if ((routerId == Get<Mle>().mRouterId) || (routerId == aNeighborId))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
router = Get<RouterTable>().FindRouterById(routerId);
|
|
|
|
if ((router == nullptr) || !router->IsStateValid())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
localLinkQuality = router->GetTwoWayLinkQuality();
|
|
|
|
if (localLinkQuality < kLinkQuality2)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// `router` is our neighbor with two-way link quality of
|
|
// at least two. Check that `aRouteTlvData` has as good or
|
|
// better-quality link to it as well.
|
|
|
|
entry = aRouteTlvData.GetEntries().FindMatching(routerId);
|
|
|
|
if (entry == nullptr)
|
|
{
|
|
ExitNow(isComparable = false);
|
|
}
|
|
|
|
peerLinkQuality = Min(entry->GetLinkQualityIn(), entry->GetLinkQualityOut());
|
|
|
|
if (peerLinkQuality < localLinkQuality)
|
|
{
|
|
ExitNow(isComparable = false);
|
|
}
|
|
}
|
|
|
|
exit:
|
|
return isComparable;
|
|
}
|
|
|
|
void Mle::SetChildStateToValid(Child &aChild)
|
|
{
|
|
VerifyOrExit(!aChild.IsStateValid());
|
|
|
|
aChild.SetState(Neighbor::kStateValid);
|
|
IgnoreError(mChildTable.StoreChild(aChild));
|
|
|
|
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
|
|
Get<Mlr::Manager>().UpdateChildRegistrations(aChild);
|
|
#endif
|
|
|
|
mNeighborTable.Signal(NeighborTable::kChildAdded, aChild);
|
|
|
|
exit:
|
|
return;
|
|
}
|
|
|
|
bool Mle::HasChildren(void) { return mChildTable.HasChildren(Child::kInStateValidOrAttaching); }
|
|
|
|
void Mle::RemoveChildren(void)
|
|
{
|
|
for (Child &child : Get<ChildTable>().Iterate(Child::kInStateValidOrRestoring))
|
|
{
|
|
RemoveNeighbor(child);
|
|
}
|
|
}
|
|
|
|
Error Mle::SetAssignParentPriority(int8_t aParentPriority)
|
|
{
|
|
Error error = kErrorNone;
|
|
|
|
VerifyOrExit(aParentPriority <= kParentPriorityHigh && aParentPriority >= kParentPriorityUnspecified,
|
|
error = kErrorInvalidArgs);
|
|
|
|
mParentPriority = aParentPriority;
|
|
|
|
exit:
|
|
return error;
|
|
}
|
|
|
|
Error Mle::GetMaxChildTimeout(uint32_t &aTimeout) const
|
|
{
|
|
Error error = kErrorNotFound;
|
|
|
|
aTimeout = 0;
|
|
|
|
VerifyOrExit(IsRouterOrLeader(), error = kErrorInvalidState);
|
|
|
|
for (Child &child : Get<ChildTable>().Iterate(Child::kInStateValid))
|
|
{
|
|
if (child.IsFullThreadDevice())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (child.GetTimeout() > aTimeout)
|
|
{
|
|
aTimeout = child.GetTimeout();
|
|
}
|
|
|
|
error = kErrorNone;
|
|
}
|
|
|
|
exit:
|
|
return error;
|
|
}
|
|
|
|
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
|
|
Error Mle::SendTimeSync(void)
|
|
{
|
|
Error error = kErrorNone;
|
|
Ip6::Address destination;
|
|
TxMessage *message = nullptr;
|
|
|
|
VerifyOrExit((message = NewMleMessage(kCommandTimeSync)) != nullptr, error = kErrorNoBufs);
|
|
|
|
message->SetTimeSync(true);
|
|
|
|
destination = Ip6::Address::GetLinkLocalAllNodesMulticast();
|
|
SuccessOrExit(error = message->SendTo(destination));
|
|
|
|
Log(kMessageSend, kTypeTimeSync, destination);
|
|
|
|
exit:
|
|
FreeMessageOnError(message, error);
|
|
return error;
|
|
}
|
|
#endif // OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
|
|
|
|
#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
|
|
|
|
const char *Mle::RouterUpgradeReasonToString(uint8_t aReason)
|
|
{
|
|
const char *str = "Unknown";
|
|
|
|
switch (aReason)
|
|
{
|
|
case kReasonTooFewRouters:
|
|
str = "TooFewRouters";
|
|
break;
|
|
case kReasonHaveChildIdRequest:
|
|
str = "HaveChildIdRequest";
|
|
break;
|
|
case kReasonParentPartitionChange:
|
|
str = "ParentPartitionChange";
|
|
break;
|
|
case kReasonBorderRouterRequest:
|
|
str = "BorderRouterRequest";
|
|
break;
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
#endif // OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
// RoleTransitioner
|
|
|
|
Mle::RoleTransitioner::RoleTransitioner(Instance &aInstance)
|
|
: InstanceLocator(aInstance)
|
|
, mRouterEligible(true)
|
|
, mRouterRoleAllowed(true)
|
|
, mDowngradeBlocked(false)
|
|
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
|
|
, mCcmEnabled(false)
|
|
, mThreadVersionCheckEnabled(true)
|
|
#endif
|
|
, mTimeout(0)
|
|
, mJitter(kRouterSelectionJitter)
|
|
, mUpgradeThreshold(kRouterUpgradeThreshold)
|
|
, mDowngradeThreshold(kRouterDowngradeThreshold)
|
|
|
|
{
|
|
}
|
|
|
|
void Mle::RoleTransitioner::StartTimeout(void) { mTimeout = 1 + Random::NonCrypto::GenerateUpToExcluding(mJitter); }
|
|
|
|
bool Mle::RoleTransitioner::IsRouterCountBelowUpgradeThreshold(void) const
|
|
{
|
|
return Get<RouterTable>().GetActiveRouterCount() < mUpgradeThreshold;
|
|
}
|
|
|
|
void Mle::RoleTransitioner::DecideWhetherToUpgrade(void)
|
|
{
|
|
VerifyOrExit(Get<Mle>().IsChild());
|
|
|
|
VerifyOrExit(IsRouterRoleAllowed());
|
|
|
|
VerifyOrExit(!IsTransitionPending());
|
|
VerifyOrExit(IsRouterCountBelowUpgradeThreshold());
|
|
|
|
StartTimeout();
|
|
|
|
exit:
|
|
return;
|
|
}
|
|
|
|
void Mle::RoleTransitioner::HandleTimeTick(void)
|
|
{
|
|
VerifyOrExit(mTimeout > 0);
|
|
mTimeout--;
|
|
|
|
VerifyOrExit(mTimeout == 0);
|
|
|
|
// Timeout expired
|
|
|
|
switch (Get<Mle>().mRole)
|
|
{
|
|
case kRoleDisabled:
|
|
case kRoleDetached:
|
|
break;
|
|
|
|
case kRoleChild:
|
|
if (IsRouterCountBelowUpgradeThreshold() && Get<Mle>().HasNeighborWithGoodLinkQuality())
|
|
{
|
|
IgnoreError(Get<Mle>().BecomeRouter(kReasonTooFewRouters));
|
|
}
|
|
else
|
|
{
|
|
Get<Mle>().mAnnounceHandler.HandleRouterRoleTransitionAttemptDone();
|
|
}
|
|
|
|
if (!Get<Mle>().mAdvertiseTrickleTimer.IsRunning())
|
|
{
|
|
Get<Mle>().SendMulticastAdvertisement();
|
|
Get<Mle>().mAdvertiseTrickleTimer.Start(TrickleTimer::kModePlainTimer, kReedAdvIntervalMin,
|
|
kReedAdvIntervalMax);
|
|
}
|
|
|
|
break;
|
|
|
|
case kRoleRouter:
|
|
if (Get<RouterTable>().GetActiveRouterCount() > mDowngradeThreshold)
|
|
{
|
|
LogNote("Downgrade to REED");
|
|
Get<Mle>().mAttacher.Attach(kDowngradeToReed);
|
|
}
|
|
|
|
OT_FALL_THROUGH;
|
|
|
|
case kRoleLeader:
|
|
if (!IsRouterRoleAllowed())
|
|
{
|
|
LogInfo("Router role no longer allowed");
|
|
IgnoreError(Get<Mle>().BecomeDetached());
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
exit:
|
|
return;
|
|
}
|
|
|
|
} // namespace Mle
|
|
} // namespace ot
|
|
|
|
#endif // OPENTHREAD_FTD
|