/* * 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().SetAlternateShortAddress(aRloc16); mAlternateRloc16Timeout = kAlternateRloc16Timeout; exit: return; } void Mle::ClearAlternateRloc16(void) { VerifyOrExit(Get().GetAlternateShortAddress() != Mac::kShortAddrInvalid); LogInfo("Clearing alternate RLOC16"); Get().SetAlternateShortAddress(Mac::kShortAddrInvalid); exit: mAlternateRloc16Timeout = 0; } void Mle::HandlePartitionChange(void) { mPreviousPartitionId = mLeaderData.GetPartitionId(); mPreviousPartitionRouterIdSequence = mRouterTable.GetRouterIdSequence(); mPreviousPartitionIdTimeout = GetNetworkIdTimeout(); Get().Clear(); IgnoreError(Get().AbortTransaction(HandleAddressSolicitResponse, this)); mRouterTable.Clear(); } bool Mle::RoleTransitioner::DetermineIfRouterRoleAllowed(void) const { bool allowed = false; const SecurityPolicy &secPolicy = Get().GetSecurityPolicy(); VerifyOrExit(mRouterEligible && Get().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().IsAttached()) { Get().SetBeaconEnabled(mRouterRoleAllowed); } // Take action based on the current role, the new `mRouterRoleAllowed`, // and the reason for the change. if (Get().IsChild() && mRouterRoleAllowed && (aReason == kReasonConfigParameterChanged)) { StartTimeout(); } if (Get().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().BecomeDetached()); break; case kReasonSecurityPolicyChanged: VerifyOrExit(!IsTransitionPending()); StartTimeout(); if (Get().IsLeader()) { IncreaseTimeout(kLeaderDowngradeExtraDelay); } break; } } exit: return; } Error Mle::RoleTransitioner::SetRouterEligible(bool aEligible) { Error error = kErrorNone; if (!Get().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().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().IsPartiallyComplete(), error = kErrorInvalidState); #else VerifyOrExit(Get().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().GetExtAddress()); Get().Reset(); Get().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(); } return partitionId; } void Mle::StopLeader(void) { StopAdvertiseTrickleTimer(); Get().UnsubscribeAllRoutersMulticast(); } void Mle::HandleDetachStart(void) { mRouterTable.ClearNeighbors(); StopLeader(); Get().UnregisterReceiver(TimeTicker::kMle); } void Mle::HandleChildStart(void) { mAddressSolicitRejected = false; mRoleTransitioner.StartTimeout(); StopLeader(); Get().RegisterReceiver(TimeTicker::kMle); Get().SetBeaconEnabled(IsRouterRoleAllowed()); Get().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().Restore()); IgnoreError(Get().Restore()); } SetRloc16(aRloc16); SetRole(aRole); mPrevRoleRestorer.Stop(); mAttacher.CancelAttachOnRoleChange(); mRetxTracker.Stop(); StopAdvertiseTrickleTimer(); ResetAdvertiseInterval(); Get().SubscribeAllRoutersMulticast(); mPreviousPartitionIdRouter = mLeaderData.GetPartitionId(); Get().SetBeaconEnabled(true); Get().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().AddUnicastAddress(mLeaderAloc); Get().Start(aStartMode); Get().StartLeader(); Get().StartLeader(); Get().Clear(); } // Remove children that do not have a matching RLOC16 for (Child &child : Get().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().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().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().ComputeLinkMargin(aRxInfo.mMessage.GetAverageRss()); switch (Tlv::Find(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(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(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(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(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().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().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().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().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().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().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().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().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(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().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(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(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(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().ComputeLinkMargin(mParent.GetLinkInfo().GetLastRss()); if (linkMargin >= kLinkRequestMinMargin) { ExitNow(); } for (const Router &router : Get()) { if (!router.IsStateValid()) { continue; } linkMargin = Get().ComputeLinkMargin(router.GetLinkInfo().GetLastRss()); if (linkMargin >= kLinkRequestMinMargin) { ExitNow(); } } haveNeighbor = false; exit: return haveNeighbor; } void Mle::HandleTimeTick(void) { VerifyOrExit(IsFullThreadDevice(), Get().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().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().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()) { 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().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().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().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().RemoveEntryForAddress(address); } #if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE Get().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().RemoveMessagesForChild(*child, IsMessageMleSubType); SuccessOrExit(error = aRxInfo.mMessage.ReadFrameCounterTlvs(linkFrameCounter, mleFrameCounter)); SuccessOrExit(error = aRxInfo.mMessage.ReadModeTlv(mode)); SuccessOrExit(error = Tlv::Find(aRxInfo.mMessage, timeout)); SuccessOrExit(error = aRxInfo.mMessage.ReadTlvRequestTlv(tlvList)); switch (Tlv::Find(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(aRxInfo.mMessage, timestamp)) { case kErrorNone: if (timestamp == Get().GetTimestamp()) { break; } OT_FALL_THROUGH; case kErrorNotFound: tlvList.Add(Tlv::kActiveDataset); break; default: ExitNow(error = kErrorParse); } switch (Tlv::Find(aRxInfo.mMessage, timestamp)) { case kErrorNone: if (timestamp == Get().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(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(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(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(aRxInfo.mMessage, cslChannelTlvValue) == kErrorNone) { // Special value of zero is used to indicate that // CSL channel is not specified. child->SetCslChannel(static_cast(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().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().Contains(*aRxInfo.mNeighbor)) { Log(kMessageReceive, kTypeChildUpdateResponseOfUnknownChild, aRxInfo.mMessageInfo.GetPeerAddr()); ExitNow(error = kErrorNotFound); } child = static_cast(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(aRxInfo.mMessage, sourceAddress)) { case kErrorNone: if (child->GetRloc16() != sourceAddress) { RemoveNeighbor(*child); ExitNow(); } break; case kErrorNotFound: break; default: ExitNow(error = kErrorParse); } switch (Tlv::Find(aRxInfo.mMessage, status)) { case kErrorNone: VerifyOrExit(status != kStatusError, RemoveNeighbor(*child)); break; case kErrorNotFound: break; default: ExitNow(error = kErrorParse); } switch (Tlv::Find(aRxInfo.mMessage, linkFrameCounter)) { case kErrorNone: child->GetLinkFrameCounters().SetAll(linkFrameCounter); child->SetLinkAckFrameCounter(linkFrameCounter); break; case kErrorNotFound: break; default: ExitNow(error = kErrorParse); } switch (Tlv::Find(aRxInfo.mMessage, mleFrameCounter)) { case kErrorNone: child->SetMleFrameCounter(mleFrameCounter); break; case kErrorNotFound: break; default: ExitNow(error = kErrorNone); } switch (Tlv::Find(aRxInfo.mMessage, timeout)) { case kErrorNone: child->SetTimeout(timeout); break; case kErrorNotFound: break; default: ExitNow(error = kErrorParse); } { uint16_t supervisionInterval; switch (Tlv::Find(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(aRxInfo.mMessage, timestamp)) { case kErrorNone: if (timestamp == Get().GetTimestamp()) { break; } OT_FALL_THROUGH; case kErrorNotFound: tlvList.Add(Tlv::kActiveDataset); break; default: ExitNow(error = kErrorParse); } switch (Tlv::Find(aRxInfo.mMessage, timestamp)) { case kErrorNone: if (timestamp == Get().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().Iterate(Child::kInStateValid)) { if (child.IsRxOnWhenIdle()) { continue; } if (child.GetNetworkDataVersion() == Get().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(aRxInfo.mMessage, discoveryRequestTlvValue)); parsedDiscoveryRequestTlv = true; break; case MeshCoP::Tlv::kExtendedPanId: SuccessOrExit(error = tlvInfo.Read(aRxInfo.mMessage, extPanId)); VerifyOrExit(Get().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().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().GetSecurityPolicy().mNativeCommissioningEnabled) { SuccessOrExit(error = Tlv::Append( *message, Get().GetUdpPort())); discoveryResponseTlvValue.SetNativeCommissionerFlag(); } #endif if (Get().GetSecurityPolicy().mCommercialCommissioningEnabled) { discoveryResponseTlvValue.SetCcmFlag(); } SuccessOrExit(error = Tlv::Append(*message, discoveryResponseTlvValue)); SuccessOrExit(error = Tlv::Append(*message, Get().GetExtPanId())); SuccessOrExit(error = Tlv::Append( *message, Get().GetNetworkName().GetAsCString())); SuccessOrExit(error = message->AppendSteeringDataTlv()); SuccessOrExit( error = Tlv::Append(*message, Get().GetJoinerUdpPort())); #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_4) if (!Get().IsDefaultDomainNameSet()) { SuccessOrExit(error = Tlv::Append( *message, Get().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().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().HasQueuedMessageForSleepyChild(aChild, IsMessageChildUpdateRequest)); } Get().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().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().ClearAllMessagesForSleepyChild(static_cast(aNeighbor)); if (aNeighbor.IsFullThreadDevice()) { Get().RemoveEntriesForRloc16(aNeighbor.GetRloc16()); } mChildTable.RemoveStoredChild(static_cast(aNeighbor)); } else if (aNeighbor.IsStateValid()) { OT_ASSERT(mRouterTable.Contains(aNeighbor)); mNeighborTable.Signal(NeighborTable::kRouterRemoved, aNeighbor); mRouterTable.RemoveRouterLink(static_cast(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().AllocateAndInitPriorityConfirmablePostMessage(kUriAddressSolicit); VerifyOrExit(message != nullptr, error = kErrorNoBufs); SuccessOrExit(error = Tlv::Append(*message, Get().GetExtAddress())); if (IsRouterIdValid(mPreviousRouterId)) { SuccessOrExit(error = Tlv::Append(*message, Rloc16FromRouterId(mPreviousRouterId))); } SuccessOrExit(error = Tlv::Append(*message, aReason)); #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE SuccessOrExit(error = Tlv::Append(*message, otPlatTimeGetXtalAccuracy())); #endif GetLeaderRloc(leaderRloc); SuccessOrExit(error = Get().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().AllocateAndInitPriorityConfirmablePostMessage(kUriAddressRelease); VerifyOrExit(message != nullptr, error = kErrorNoBufs); SuccessOrExit(error = Tlv::Append(*message, Rloc16FromRouterId(mRouterId))); SuccessOrExit(error = Tlv::Append(*message, Get().GetExtAddress())); GetLeaderRloc(leaderRloc); SuccessOrExit(error = Get().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(aMsg->mMessage, status)); if (status != kAddrSolicitSuccess) { mAddressSolicitRejected = true; if (IsRouterIdValid(mPreviousRouterId)) { if (HasChildren()) { RemoveChildren(); } SetRouterId(kInvalidRouterId); } ExitNow(); } SuccessOrExit(Tlv::Find(aMsg->mMessage, rloc16)); routerId = RouterIdFromRloc16(rloc16); SuccessOrExit(Tlv::Find(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().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().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().IsChild()); VerifyOrExit(!Get().mAddressSolicitRejected); if (!Get().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(aMsg.mMessage, mExtAddress)); SuccessOrExit(error = Tlv::Find(aMsg.mMessage, mReason)); switch (Tlv::Find(aMsg.mMessage, mRequestedRloc16)) { case kErrorNone: break; case kErrorNotFound: mRequestedRloc16 = kInvalidRloc16; break; default: ExitNow(error = kErrorParse); } #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE switch (Tlv::Find(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().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().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(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().AllocateAndInitPriorityResponseFor(aMsg.mMessage); VerifyOrExit(response != nullptr); SuccessOrExit(Tlv::Append(*response, info.mResponse)); if (info.mRouter != nullptr) { RouterIdMask routerIdMask; SuccessOrExit(Tlv::Append(*response, info.mRouter->GetRloc16())); mRouterTable.GetRouterIdMask(routerIdMask); SuccessOrExit(Tlv::Append(*response, routerIdMask)); } SuccessOrExit(Get().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().RemoveEntriesForRloc16(oldRloc16); } exit: FreeMessage(response); } template <> void Mle::HandleTmf(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(aMsg.mMessage, rloc16)); SuccessOrExit(Tlv::Find(aMsg.mMessage, extAddress)); routerId = RouterIdFromRloc16(rloc16); router = mRouterTable.FindRouterById(routerId); VerifyOrExit((router != nullptr) && (router->GetExtAddress() == extAddress)); IgnoreError(mRouterTable.Release(routerId)); SuccessOrExit(Get().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()) { 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().GetActiveRouterCount(); uint8_t count; VerifyOrExit(Get().IsRouter()); VerifyOrExit(Get().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()) { 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().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().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().mRouterId) || (routerId == aNeighborId)) { continue; } router = Get().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().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().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().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().GetActiveRouterCount() < mUpgradeThreshold; } void Mle::RoleTransitioner::DecideWhetherToUpgrade(void) { VerifyOrExit(Get().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().mRole) { case kRoleDisabled: case kRoleDetached: break; case kRoleChild: if (IsRouterCountBelowUpgradeThreshold() && Get().HasNeighborWithGoodLinkQuality()) { IgnoreError(Get().BecomeRouter(kReasonTooFewRouters)); } else { Get().mAnnounceHandler.HandleRouterRoleTransitionAttemptDone(); } if (!Get().mAdvertiseTrickleTimer.IsRunning()) { Get().SendMulticastAdvertisement(); Get().mAdvertiseTrickleTimer.Start(TrickleTimer::kModePlainTimer, kReedAdvIntervalMin, kReedAdvIntervalMax); } break; case kRoleRouter: if (Get().GetActiveRouterCount() > mDowngradeThreshold) { LogNote("Downgrade to REED"); Get().mAttacher.Attach(kDowngradeToReed); } OT_FALL_THROUGH; case kRoleLeader: if (!IsRouterRoleAllowed()) { LogInfo("Router role no longer allowed"); IgnoreError(Get().BecomeDetached()); } break; } exit: return; } } // namespace Mle } // namespace ot #endif // OPENTHREAD_FTD