blob: f84d04e02a45b4eac040fcbb5dd063567853b361 [file] [log] [blame]
/*
* 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 mesh forwarding of IPv6/6LoWPAN messages.
*/
#include "mesh_forwarder.hpp"
#include "common/code_utils.hpp"
#include "common/debug.hpp"
#include "common/encoding.hpp"
#include "common/instance.hpp"
#include "common/locator_getters.hpp"
#include "common/message.hpp"
#include "common/random.hpp"
#include "common/time_ticker.hpp"
#include "net/ip6.hpp"
#include "net/ip6_filter.hpp"
#include "net/netif.hpp"
#include "net/tcp6.hpp"
#include "net/udp6.hpp"
#include "radio/radio.hpp"
#include "thread/mle.hpp"
#include "thread/mle_router.hpp"
#include "thread/thread_netif.hpp"
namespace ot {
RegisterLogModule("MeshForwarder");
void ThreadLinkInfo::SetFrom(const Mac::RxFrame &aFrame)
{
Clear();
if (kErrorNone != aFrame.GetSrcPanId(mPanId))
{
IgnoreError(aFrame.GetDstPanId(mPanId));
}
{
Mac::PanId dstPanId;
if (kErrorNone != aFrame.GetDstPanId(dstPanId))
{
dstPanId = mPanId;
}
mIsDstPanIdBroadcast = (dstPanId == Mac::kPanIdBroadcast);
}
if (aFrame.GetSecurityEnabled())
{
uint8_t keyIdMode;
// MAC Frame Security was already validated at the MAC
// layer. As a result, `GetKeyIdMode()` will never return
// failure here.
IgnoreError(aFrame.GetKeyIdMode(keyIdMode));
mLinkSecurity = (keyIdMode == Mac::Frame::kKeyIdMode0) || (keyIdMode == Mac::Frame::kKeyIdMode1);
}
else
{
mLinkSecurity = false;
}
mChannel = aFrame.GetChannel();
mRss = aFrame.GetRssi();
mLqi = aFrame.GetLqi();
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
if (aFrame.GetTimeIe() != nullptr)
{
mNetworkTimeOffset = aFrame.ComputeNetworkTimeOffset();
mTimeSyncSeq = aFrame.ReadTimeSyncSeq();
}
#endif
#if OPENTHREAD_CONFIG_MULTI_RADIO
mRadioType = static_cast<uint8_t>(aFrame.GetRadioType());
#endif
}
MeshForwarder::MeshForwarder(Instance &aInstance)
: InstanceLocator(aInstance)
, mMessageNextOffset(0)
, mSendMessage(nullptr)
, mMeshSource()
, mMeshDest()
, mAddMeshHeader(false)
, mEnabled(false)
, mTxPaused(false)
, mSendBusy(false)
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_COLLISION_AVOIDANCE_DELAY_ENABLE
, mDelayNextTx(false)
, mTxDelayTimer(aInstance)
#endif
, mScheduleTransmissionTask(aInstance)
#if OPENTHREAD_FTD
, mIndirectSender(aInstance)
#endif
, mDataPollSender(aInstance)
{
mFragTag = Random::NonCrypto::GetUint16();
ResetCounters();
#if OPENTHREAD_FTD
mFragmentPriorityList.Clear();
#endif
#if OPENTHREAD_CONFIG_TX_QUEUE_STATISTICS_ENABLE
mTxQueueStats.Clear();
#endif
}
void MeshForwarder::Start(void)
{
if (!mEnabled)
{
Get<Mac::Mac>().SetRxOnWhenIdle(true);
#if OPENTHREAD_FTD
mIndirectSender.Start();
#endif
mEnabled = true;
}
}
void MeshForwarder::Stop(void)
{
VerifyOrExit(mEnabled);
mDataPollSender.StopPolling();
Get<TimeTicker>().UnregisterReceiver(TimeTicker::kMeshForwarder);
Get<Mle::DiscoverScanner>().Stop();
mSendQueue.DequeueAndFreeAll();
mReassemblyList.DequeueAndFreeAll();
#if OPENTHREAD_FTD
mIndirectSender.Stop();
mFragmentPriorityList.Clear();
#endif
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_COLLISION_AVOIDANCE_DELAY_ENABLE
mTxDelayTimer.Stop();
mDelayNextTx = false;
#endif
mEnabled = false;
mSendMessage = nullptr;
Get<Mac::Mac>().SetRxOnWhenIdle(false);
exit:
return;
}
void MeshForwarder::PrepareEmptyFrame(Mac::TxFrame &aFrame, const Mac::Address &aMacDest, bool aAckRequest)
{
Mac::Addresses addresses;
Mac::PanIds panIds;
addresses.mSource.SetShort(Get<Mac::Mac>().GetShortAddress());
if (addresses.mSource.IsShortAddrInvalid() || aMacDest.IsExtended())
{
addresses.mSource.SetExtended(Get<Mac::Mac>().GetExtAddress());
}
addresses.mDestination = aMacDest;
panIds.SetBothSourceDestination(Get<Mac::Mac>().GetPanId());
PrepareMacHeaders(aFrame, Mac::Frame::kTypeData, addresses, panIds, Mac::Frame::kSecurityEncMic32,
Mac::Frame::kKeyIdMode1, nullptr);
aFrame.SetAckRequest(aAckRequest);
aFrame.SetPayloadLength(0);
}
void MeshForwarder::RemoveMessage(Message &aMessage)
{
PriorityQueue *queue = aMessage.GetPriorityQueue();
OT_ASSERT(queue != nullptr);
if (queue == &mSendQueue)
{
#if OPENTHREAD_FTD
for (Child &child : Get<ChildTable>().Iterate(Child::kInStateAnyExceptInvalid))
{
IgnoreError(mIndirectSender.RemoveMessageFromSleepyChild(aMessage, child));
}
#endif
if (mSendMessage == &aMessage)
{
mSendMessage = nullptr;
}
}
LogMessage(kMessageEvict, aMessage, kErrorNoBufs);
queue->DequeueAndFree(aMessage);
}
void MeshForwarder::ResumeMessageTransmissions(void)
{
if (mTxPaused)
{
mTxPaused = false;
mScheduleTransmissionTask.Post();
}
}
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_COLLISION_AVOIDANCE_DELAY_ENABLE
void MeshForwarder::HandleTxDelayTimer(void)
{
mDelayNextTx = false;
mScheduleTransmissionTask.Post();
LogDebg("Tx delay timer expired");
}
#endif
#if OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_ENABLE
Error MeshForwarder::UpdateEcnOrDrop(Message &aMessage, bool aPreparingToSend)
{
// This method performs delay-aware active queue management for
// direct message transmission. It parses the IPv6 header from
// `aMessage` to determine if message is ECN-capable. This is
// then used along with the message's time-in-queue to decide
// whether to keep the message as is, change the ECN field to
// mark congestion, or drop the message. If the message is to be
// dropped, this method clears the direct tx flag on `aMessage`
// and removes it from the send queue (if no pending indirect tx)
// and returns `kErrorDrop`. This method returns `kErrorNone`
// when the message is kept as is or ECN field is updated.
Error error = kErrorNone;
uint32_t timeInQueue = TimerMilli::GetNow() - aMessage.GetTimestamp();
bool shouldMarkEcn = (timeInQueue >= kTimeInQueueMarkEcn);
bool isEcnCapable = false;
VerifyOrExit(aMessage.IsDirectTransmission() && (aMessage.GetOffset() == 0));
if (aMessage.GetType() == Message::kTypeIp6)
{
Ip6::Header ip6Header;
IgnoreError(aMessage.Read(0, ip6Header));
VerifyOrExit(!Get<ThreadNetif>().HasUnicastAddress(ip6Header.GetSource()));
isEcnCapable = (ip6Header.GetEcn() != Ip6::kEcnNotCapable);
if ((shouldMarkEcn && !isEcnCapable) || (timeInQueue >= kTimeInQueueDropMsg))
{
ExitNow(error = kErrorDrop);
}
if (shouldMarkEcn)
{
switch (ip6Header.GetEcn())
{
case Ip6::kEcnCapable0:
case Ip6::kEcnCapable1:
ip6Header.SetEcn(Ip6::kEcnMarked);
aMessage.Write(0, ip6Header);
LogMessage(kMessageMarkEcn, aMessage);
break;
case Ip6::kEcnMarked:
case Ip6::kEcnNotCapable:
break;
}
}
}
#if OPENTHREAD_FTD
else if (aMessage.GetType() == Message::kType6lowpan)
{
uint16_t headerLength = 0;
uint16_t offset;
bool hasFragmentHeader = false;
Lowpan::FragmentHeader fragmentHeader;
Lowpan::MeshHeader meshHeader;
IgnoreError(meshHeader.ParseFrom(aMessage, headerLength));
offset = headerLength;
if (fragmentHeader.ParseFrom(aMessage, offset, headerLength) == kErrorNone)
{
hasFragmentHeader = true;
offset += headerLength;
}
if (!hasFragmentHeader || (fragmentHeader.GetDatagramOffset() == 0))
{
Ip6::Ecn ecn = Get<Lowpan::Lowpan>().DecompressEcn(aMessage, offset);
isEcnCapable = (ecn != Ip6::kEcnNotCapable);
if ((shouldMarkEcn && !isEcnCapable) || (timeInQueue >= kTimeInQueueDropMsg))
{
FragmentPriorityList::Entry *entry;
entry = mFragmentPriorityList.FindEntry(meshHeader.GetSource(), fragmentHeader.GetDatagramTag());
if (entry != nullptr)
{
entry->MarkToDrop();
entry->ResetLifetime();
}
ExitNow(error = kErrorDrop);
}
if (shouldMarkEcn)
{
switch (ecn)
{
case Ip6::kEcnCapable0:
case Ip6::kEcnCapable1:
Get<Lowpan::Lowpan>().MarkCompressedEcn(aMessage, offset);
LogMessage(kMessageMarkEcn, aMessage);
break;
case Ip6::kEcnMarked:
case Ip6::kEcnNotCapable:
break;
}
}
}
else if (hasFragmentHeader)
{
FragmentPriorityList::Entry *entry;
entry = mFragmentPriorityList.FindEntry(meshHeader.GetSource(), fragmentHeader.GetDatagramTag());
VerifyOrExit(entry != nullptr);
if (entry->ShouldDrop())
{
error = kErrorDrop;
}
// We can clear the entry if it is the last fragment and
// only if the message is being prepared to be sent out.
if (aPreparingToSend && (fragmentHeader.GetDatagramOffset() + aMessage.GetLength() - offset >=
fragmentHeader.GetDatagramSize()))
{
entry->Clear();
}
}
}
#else
OT_UNUSED_VARIABLE(aPreparingToSend);
#endif // OPENTHREAD_FTD
exit:
if (error == kErrorDrop)
{
#if OPENTHREAD_CONFIG_TX_QUEUE_STATISTICS_ENABLE
mTxQueueStats.UpdateFor(aMessage);
#endif
LogMessage(kMessageQueueMgmtDrop, aMessage);
aMessage.ClearDirectTransmission();
RemoveMessageIfNoPendingTx(aMessage);
}
return error;
}
Error MeshForwarder::RemoveAgedMessages(void)
{
// This method goes through all messages in the send queue and
// removes all aged messages determined based on the delay-aware
// active queue management rules. It may also mark ECN on some
// messages. It returns `kErrorNone` if at least one message was
// removed, or `kErrorNotFound` if none was removed.
Error error = kErrorNotFound;
Message *nextMessage;
for (Message *message = mSendQueue.GetHead(); message != nullptr; message = nextMessage)
{
nextMessage = message->GetNext();
// Exclude the current message being sent `mSendMessage`.
if ((message == mSendMessage) || !message->IsDirectTransmission() || message->GetDoNotEvict())
{
continue;
}
if (UpdateEcnOrDrop(*message, /* aPreparingToSend */ false) == kErrorDrop)
{
error = kErrorNone;
}
}
return error;
}
#endif // OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_ENABLE
#if (OPENTHREAD_CONFIG_MAX_FRAMES_IN_DIRECT_TX_QUEUE > 0)
bool MeshForwarder::IsDirectTxQueueOverMaxFrameThreshold(void) const
{
uint16_t frameCount = 0;
for (const Message &message : mSendQueue)
{
if (!message.IsDirectTransmission() || (&message == mSendMessage))
{
continue;
}
switch (message.GetType())
{
case Message::kTypeIp6:
{
// If it is an IPv6 message, we estimate the number of
// fragment frames assuming typical header sizes and lowpan
// compression. Since this estimate is only used for queue
// management, we lean towards an under estimate in sense
// that we may allow few more frames in the tx queue over
// threshold in some rare cases.
//
// The constants below are derived as follows: Typical MAC
// header (15 bytes) and MAC footer (6 bytes) leave 106
// bytes for MAC payload. Next fragment header is 5 bytes
// leaving 96 for next fragment payload. Lowpan compression
// on average compresses 40 bytes IPv6 header into about 19
// bytes leaving 87 bytes for the IPv6 payload, so the first
// fragment can fit 87 + 40 = 127 bytes.
static constexpr uint16_t kFirstFragmentMaxLength = 127;
static constexpr uint16_t kNextFragmentSize = 96;
uint16_t length = message.GetLength();
frameCount++;
if (length > kFirstFragmentMaxLength)
{
frameCount += (length - kFirstFragmentMaxLength) / kNextFragmentSize;
}
break;
}
case Message::kType6lowpan:
case Message::kTypeMacEmptyData:
frameCount++;
break;
case Message::kTypeSupervision:
default:
break;
}
}
return (frameCount > OPENTHREAD_CONFIG_MAX_FRAMES_IN_DIRECT_TX_QUEUE);
}
void MeshForwarder::ApplyDirectTxQueueLimit(Message &aMessage)
{
VerifyOrExit(aMessage.IsDirectTransmission());
VerifyOrExit(IsDirectTxQueueOverMaxFrameThreshold());
#if OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_ENABLE
{
bool originalEvictFlag = aMessage.GetDoNotEvict();
Error error;
// We mark the "do not evict" flag on the new `aMessage` so
// that it will not be removed from `RemoveAgedMessages()`.
// This protects against the unlikely case where the newly
// queued `aMessage` may already be aged due to execution
// being interrupted for a long time between the queuing of
// the message and the `ApplyDirectTxQueueLimit()` call. We
// do not want the message to be potentially removed and
// freed twice.
aMessage.SetDoNotEvict(true);
error = RemoveAgedMessages();
aMessage.SetDoNotEvict(originalEvictFlag);
if (error == kErrorNone)
{
VerifyOrExit(IsDirectTxQueueOverMaxFrameThreshold());
}
}
#endif
LogMessage(kMessageFullQueueDrop, aMessage);
aMessage.ClearDirectTransmission();
RemoveMessageIfNoPendingTx(aMessage);
exit:
return;
}
#endif // (OPENTHREAD_CONFIG_MAX_FRAMES_IN_DIRECT_TX_QUEUE > 0)
#if OPENTHREAD_CONFIG_TX_QUEUE_STATISTICS_ENABLE
const uint32_t *MeshForwarder::TxQueueStats::GetHistogram(uint16_t &aNumBins, uint32_t &aBinInterval) const
{
aNumBins = kNumHistBins;
aBinInterval = kHistBinInterval;
return mHistogram;
}
void MeshForwarder::TxQueueStats::UpdateFor(const Message &aMessage)
{
uint32_t timeInQueue = TimerMilli::GetNow() - aMessage.GetTimestamp();
mHistogram[Min<uint32_t>(timeInQueue / kHistBinInterval, kNumHistBins - 1)]++;
mMaxInterval = Max(mMaxInterval, timeInQueue);
}
#endif
void MeshForwarder::ScheduleTransmissionTask(void)
{
VerifyOrExit(!mSendBusy && !mTxPaused);
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_COLLISION_AVOIDANCE_DELAY_ENABLE
VerifyOrExit(!mDelayNextTx);
#endif
mSendMessage = PrepareNextDirectTransmission();
VerifyOrExit(mSendMessage != nullptr);
if (mSendMessage->GetOffset() == 0)
{
mSendMessage->SetTxSuccess(true);
}
Get<Mac::Mac>().RequestDirectFrameTransmission();
exit:
return;
}
Message *MeshForwarder::PrepareNextDirectTransmission(void)
{
Message *curMessage, *nextMessage;
Error error = kErrorNone;
for (curMessage = mSendQueue.GetHead(); curMessage; curMessage = nextMessage)
{
// We set the `nextMessage` here but it can be updated again
// after the `switch(message.GetType())` since it may be
// evicted during message processing (e.g., from the call to
// `UpdateIp6Route()` due to Address Solicit).
nextMessage = curMessage->GetNext();
if (!curMessage->IsDirectTransmission() || curMessage->IsResolvingAddress())
{
continue;
}
#if OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_ENABLE
if (UpdateEcnOrDrop(*curMessage, /* aPreparingToSend */ true) == kErrorDrop)
{
continue;
}
#endif
curMessage->SetDoNotEvict(true);
switch (curMessage->GetType())
{
case Message::kTypeIp6:
error = UpdateIp6Route(*curMessage);
break;
#if OPENTHREAD_FTD
case Message::kType6lowpan:
error = UpdateMeshRoute(*curMessage);
break;
#endif
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
case Message::kTypeMacEmptyData:
error = kErrorNone;
break;
#endif
default:
error = kErrorDrop;
break;
}
curMessage->SetDoNotEvict(false);
// the next message may have been evicted during processing (e.g. due to Address Solicit)
nextMessage = curMessage->GetNext();
switch (error)
{
case kErrorNone:
#if OPENTHREAD_CONFIG_TX_QUEUE_STATISTICS_ENABLE
mTxQueueStats.UpdateFor(*curMessage);
#endif
ExitNow();
#if OPENTHREAD_FTD
case kErrorAddressQuery:
curMessage->SetResolvingAddress(true);
continue;
#endif
default:
#if OPENTHREAD_CONFIG_TX_QUEUE_STATISTICS_ENABLE
mTxQueueStats.UpdateFor(*curMessage);
#endif
LogMessage(kMessageDrop, *curMessage, error);
mSendQueue.DequeueAndFree(*curMessage);
continue;
}
}
exit:
return curMessage;
}
Error MeshForwarder::UpdateIp6Route(Message &aMessage)
{
Mle::MleRouter &mle = Get<Mle::MleRouter>();
Error error = kErrorNone;
Ip6::Header ip6Header;
mAddMeshHeader = false;
IgnoreError(aMessage.Read(0, ip6Header));
VerifyOrExit(!ip6Header.GetSource().IsMulticast(), error = kErrorDrop);
GetMacSourceAddress(ip6Header.GetSource(), mMacAddrs.mSource);
if (mle.IsDisabled() || mle.IsDetached())
{
if (ip6Header.GetDestination().IsLinkLocal() || ip6Header.GetDestination().IsLinkLocalMulticast())
{
GetMacDestinationAddress(ip6Header.GetDestination(), mMacAddrs.mDestination);
}
else
{
error = kErrorDrop;
}
ExitNow();
}
if (ip6Header.GetDestination().IsMulticast())
{
// With the exception of MLE multicasts and any other message
// with link security disabled, an End Device transmits
// multicasts, as IEEE 802.15.4 unicasts to its parent.
if (mle.IsChild() && aMessage.IsLinkSecurityEnabled() && !aMessage.IsSubTypeMle())
{
mMacAddrs.mDestination.SetShort(mle.GetNextHop(Mac::kShortAddrBroadcast));
}
else
{
mMacAddrs.mDestination.SetShort(Mac::kShortAddrBroadcast);
}
}
else if (ip6Header.GetDestination().IsLinkLocal())
{
GetMacDestinationAddress(ip6Header.GetDestination(), mMacAddrs.mDestination);
}
else if (mle.IsMinimalEndDevice())
{
mMacAddrs.mDestination.SetShort(mle.GetNextHop(Mac::kShortAddrBroadcast));
}
else
{
#if OPENTHREAD_FTD
error = UpdateIp6RouteFtd(ip6Header, aMessage);
#else
OT_ASSERT(false);
#endif
}
exit:
return error;
}
bool MeshForwarder::GetRxOnWhenIdle(void) const { return Get<Mac::Mac>().GetRxOnWhenIdle(); }
void MeshForwarder::SetRxOnWhenIdle(bool aRxOnWhenIdle)
{
Get<Mac::Mac>().SetRxOnWhenIdle(aRxOnWhenIdle);
if (aRxOnWhenIdle)
{
mDataPollSender.StopPolling();
Get<SupervisionListener>().Stop();
}
else
{
mDataPollSender.StartPolling();
Get<SupervisionListener>().Start();
}
}
void MeshForwarder::GetMacSourceAddress(const Ip6::Address &aIp6Addr, Mac::Address &aMacAddr)
{
aIp6Addr.GetIid().ConvertToMacAddress(aMacAddr);
if (aMacAddr.GetExtended() != Get<Mac::Mac>().GetExtAddress())
{
aMacAddr.SetShort(Get<Mac::Mac>().GetShortAddress());
}
}
void MeshForwarder::GetMacDestinationAddress(const Ip6::Address &aIp6Addr, Mac::Address &aMacAddr)
{
if (aIp6Addr.IsMulticast())
{
aMacAddr.SetShort(Mac::kShortAddrBroadcast);
}
else if (Get<Mle::MleRouter>().IsRoutingLocator(aIp6Addr))
{
aMacAddr.SetShort(aIp6Addr.GetIid().GetLocator());
}
else
{
aIp6Addr.GetIid().ConvertToMacAddress(aMacAddr);
}
}
Mac::TxFrame *MeshForwarder::HandleFrameRequest(Mac::TxFrames &aTxFrames)
{
Mac::TxFrame *frame = nullptr;
bool addFragHeader = false;
VerifyOrExit(mEnabled && (mSendMessage != nullptr));
#if OPENTHREAD_CONFIG_MULTI_RADIO
frame = &Get<RadioSelector>().SelectRadio(*mSendMessage, mMacAddrs.mDestination, aTxFrames);
// If multi-radio link is supported, when sending frame with link
// security enabled, Fragment Header is always included (even if
// the message is small and does not require 6LoWPAN fragmentation).
// This allows the Fragment Header's tag to be used to detect and
// suppress duplicate received frames over different radio links.
if (mSendMessage->IsLinkSecurityEnabled())
{
addFragHeader = true;
}
#else
frame = &aTxFrames.GetTxFrame();
#endif
mSendBusy = true;
switch (mSendMessage->GetType())
{
case Message::kTypeIp6:
if (mSendMessage->GetSubType() == Message::kSubTypeMleDiscoverRequest)
{
frame = Get<Mle::DiscoverScanner>().PrepareDiscoveryRequestFrame(*frame);
VerifyOrExit(frame != nullptr);
}
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
else if (Get<Mac::Mac>().IsCslEnabled() && mSendMessage->IsSubTypeMle())
{
mSendMessage->SetLinkSecurityEnabled(true);
}
#endif
mMessageNextOffset =
PrepareDataFrame(*frame, *mSendMessage, mMacAddrs, mAddMeshHeader, mMeshSource, mMeshDest, addFragHeader);
if ((mSendMessage->GetSubType() == Message::kSubTypeMleChildIdRequest) && mSendMessage->IsLinkSecurityEnabled())
{
LogNote("Child ID Request requires fragmentation, aborting tx");
mMessageNextOffset = mSendMessage->GetLength();
ExitNow(frame = nullptr);
}
break;
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
case Message::kTypeMacEmptyData:
{
Mac::Address macDestAddr;
macDestAddr.SetShort(Get<Mle::MleRouter>().GetParent().GetRloc16());
PrepareEmptyFrame(*frame, macDestAddr, /* aAckRequest */ true);
}
break;
#endif
#if OPENTHREAD_FTD
case Message::kType6lowpan:
SendMesh(*mSendMessage, *frame);
break;
case Message::kTypeSupervision:
// A direct supervision message is possible in the case where
// a sleepy child switches its mode (becomes non-sleepy) while
// there is a pending indirect supervision message in the send
// queue for it. The message would be then converted to a
// direct tx.
OT_FALL_THROUGH;
#endif
default:
mMessageNextOffset = mSendMessage->GetLength();
ExitNow(frame = nullptr);
}
frame->SetIsARetransmission(false);
exit:
return frame;
}
void MeshForwarder::PrepareMacHeaders(Mac::TxFrame &aFrame,
Mac::Frame::Type aFrameType,
const Mac::Addresses &aMacAddrs,
const Mac::PanIds &aPanIds,
Mac::Frame::SecurityLevel aSecurityLevel,
Mac::Frame::KeyIdMode aKeyIdMode,
const Message *aMessage)
{
bool iePresent;
Mac::Frame::Version version;
iePresent = CalcIePresent(aMessage);
version = CalcFrameVersion(Get<NeighborTable>().FindNeighbor(aMacAddrs.mDestination), iePresent);
aFrame.InitMacHeader(aFrameType, version, aMacAddrs, aPanIds, aSecurityLevel, aKeyIdMode);
#if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT
if (iePresent)
{
AppendHeaderIe(aMessage, aFrame);
}
#endif
}
// This method constructs a MAC data from from a given IPv6 message.
//
// This method handles generation of MAC header, mesh header (if
// requested), lowpan compression of IPv6 header, lowpan fragmentation
// header (if message requires fragmentation or if it is explicitly
// requested by setting `aAddFragHeader` to `true`) It uses the
// message offset to construct next fragments. This method enables
// link security when message is MLE type and requires fragmentation.
// It returns the next offset into the message after the prepared
// frame.
//
uint16_t MeshForwarder::PrepareDataFrame(Mac::TxFrame &aFrame,
Message &aMessage,
const Mac::Addresses &aMacAddrs,
bool aAddMeshHeader,
uint16_t aMeshSource,
uint16_t aMeshDest,
bool aAddFragHeader)
{
Mac::Frame::SecurityLevel securityLevel;
Mac::Frame::KeyIdMode keyIdMode;
Mac::PanIds panIds;
uint16_t payloadLength;
uint16_t origMsgOffset;
uint16_t nextOffset;
FrameBuilder frameBuilder;
start:
securityLevel = Mac::Frame::kSecurityNone;
keyIdMode = Mac::Frame::kKeyIdMode1;
if (aMessage.IsLinkSecurityEnabled())
{
securityLevel = Mac::Frame::kSecurityEncMic32;
switch (aMessage.GetSubType())
{
case Message::kSubTypeJoinerEntrust:
keyIdMode = Mac::Frame::kKeyIdMode0;
break;
case Message::kSubTypeMleAnnounce:
keyIdMode = Mac::Frame::kKeyIdMode2;
break;
default:
// Use the `kKeyIdMode1`
break;
}
}
panIds.SetBothSourceDestination(Get<Mac::Mac>().GetPanId());
switch (aMessage.GetSubType())
{
case Message::kSubTypeMleAnnounce:
aFrame.SetChannel(aMessage.GetChannel());
aFrame.SetRxChannelAfterTxDone(Get<Mac::Mac>().GetPanChannel());
panIds.SetDestination(Mac::kPanIdBroadcast);
break;
case Message::kSubTypeMleDiscoverRequest:
case Message::kSubTypeMleDiscoverResponse:
panIds.SetDestination(aMessage.GetPanId());
break;
default:
break;
}
PrepareMacHeaders(aFrame, Mac::Frame::kTypeData, aMacAddrs, panIds, securityLevel, keyIdMode, &aMessage);
frameBuilder.Init(aFrame.GetPayload(), aFrame.GetMaxPayloadLength());
#if OPENTHREAD_FTD
// Initialize Mesh header
if (aAddMeshHeader)
{
Lowpan::MeshHeader meshHeader;
uint16_t maxPayloadLength;
// Mesh Header frames are forwarded by routers over multiple
// hops to reach a final destination. The forwarding path can
// have routers supporting different radio links with varying
// MTU sizes. Since the originator of the frame does not know the
// path and the MTU sizes of supported radio links by the routers
// in the path, we limit the max payload length of a Mesh Header
// frame to a fixed minimum value (derived from 15.4 radio)
// ensuring it can be handled by any radio link.
//
// Maximum payload length is calculated by subtracting the frame
// header and footer lengths from the MTU size. The footer
// length is derived by removing the `aFrame.GetFcsSize()` and
// then adding the fixed `kMeshHeaderFrameFcsSize` instead
// (updating the FCS size in the calculation of footer length).
maxPayloadLength = kMeshHeaderFrameMtu - aFrame.GetHeaderLength() -
(aFrame.GetFooterLength() - aFrame.GetFcsSize() + kMeshHeaderFrameFcsSize);
frameBuilder.Init(aFrame.GetPayload(), maxPayloadLength);
meshHeader.Init(aMeshSource, aMeshDest, kMeshHeaderHopsLeft);
IgnoreError(meshHeader.AppendTo(frameBuilder));
}
#endif // OPENTHREAD_FTD
// While performing lowpan compression, the message offset may be
// changed to skip over the compressed IPv6 headers, we save the
// original offset and set it back on `aMessage` at the end
// before returning.
origMsgOffset = aMessage.GetOffset();
// Compress IPv6 Header
if (aMessage.GetOffset() == 0)
{
uint16_t fragHeaderOffset;
uint16_t maxFrameLength;
Mac::Addresses macAddrs;
// Before performing lowpan header compression, we reduce the
// max length on `frameBuilder` to reserve bytes for first
// fragment header. This ensures that lowpan compression will
// leave room for a first fragment header. After the lowpan
// header compression is done, we reclaim the reserved bytes
// by setting the max length back to its original value.
fragHeaderOffset = frameBuilder.GetLength();
maxFrameLength = frameBuilder.GetMaxLength();
frameBuilder.SetMaxLength(maxFrameLength - sizeof(Lowpan::FragmentHeader::FirstFrag));
if (aAddMeshHeader)
{
macAddrs.mSource.SetShort(aMeshSource);
macAddrs.mDestination.SetShort(aMeshDest);
}
else
{
macAddrs = aMacAddrs;
}
SuccessOrAssert(Get<Lowpan::Lowpan>().Compress(aMessage, macAddrs, frameBuilder));
frameBuilder.SetMaxLength(maxFrameLength);
payloadLength = aMessage.GetLength() - aMessage.GetOffset();
if (aAddFragHeader || (payloadLength > frameBuilder.GetRemainingLength()))
{
Lowpan::FragmentHeader::FirstFrag firstFragHeader;
if ((!aMessage.IsLinkSecurityEnabled()) && aMessage.IsSubTypeMle())
{
// MLE messages that require fragmentation MUST use
// link-layer security. We enable security and try
// constructing the frame again.
aMessage.SetOffset(0);
aMessage.SetLinkSecurityEnabled(true);
goto start;
}
// Insert Fragment header
if (aMessage.GetDatagramTag() == 0)
{
// Avoid using datagram tag value 0, which indicates the tag has not been set
if (mFragTag == 0)
{
mFragTag++;
}
aMessage.SetDatagramTag(mFragTag++);
}
firstFragHeader.Init(aMessage.GetLength(), static_cast<uint16_t>(aMessage.GetDatagramTag()));
SuccessOrAssert(frameBuilder.Insert(fragHeaderOffset, firstFragHeader));
}
}
else
{
Lowpan::FragmentHeader::NextFrag nextFragHeader;
nextFragHeader.Init(aMessage.GetLength(), static_cast<uint16_t>(aMessage.GetDatagramTag()),
aMessage.GetOffset());
SuccessOrAssert(frameBuilder.Append(nextFragHeader));
payloadLength = aMessage.GetLength() - aMessage.GetOffset();
}
if (payloadLength > frameBuilder.GetRemainingLength())
{
payloadLength = (frameBuilder.GetRemainingLength() & ~0x7);
}
// Copy IPv6 Payload
SuccessOrAssert(frameBuilder.AppendBytesFromMessage(aMessage, aMessage.GetOffset(), payloadLength));
aFrame.SetPayloadLength(frameBuilder.GetLength());
nextOffset = aMessage.GetOffset() + payloadLength;
if (nextOffset < aMessage.GetLength())
{
aFrame.SetFramePending(true);
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
aMessage.SetTimeSync(false);
#endif
}
aMessage.SetOffset(origMsgOffset);
return nextOffset;
}
uint16_t MeshForwarder::PrepareDataFrameWithNoMeshHeader(Mac::TxFrame &aFrame,
Message &aMessage,
const Mac::Addresses &aMacAddrs)
{
return PrepareDataFrame(aFrame, aMessage, aMacAddrs, /* aAddMeshHeader */ false, /* aMeshSource */ 0xffff,
/* aMeshDest */ 0xffff, /* aAddFragHeader */ false);
}
Neighbor *MeshForwarder::UpdateNeighborOnSentFrame(Mac::TxFrame &aFrame,
Error aError,
const Mac::Address &aMacDest,
bool aIsDataPoll)
{
OT_UNUSED_VARIABLE(aIsDataPoll);
Neighbor *neighbor = nullptr;
uint8_t failLimit = kFailedRouterTransmissions;
VerifyOrExit(mEnabled);
neighbor = Get<NeighborTable>().FindNeighbor(aMacDest);
VerifyOrExit(neighbor != nullptr);
VerifyOrExit(aFrame.GetAckRequest());
#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
// TREL radio link uses deferred ack model. We ignore
// `SendDone` event from `Mac` layer with success status and
// wait for deferred ack callback instead.
#if OPENTHREAD_CONFIG_MULTI_RADIO
if (aFrame.GetRadioType() == Mac::kRadioTypeTrel)
#endif
{
VerifyOrExit(aError != kErrorNone);
}
#endif // OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
if ((aFrame.GetHeaderIe(Mac::CslIe::kHeaderIeId) != nullptr) && aIsDataPoll)
{
failLimit = kFailedCslDataPollTransmissions;
}
#endif
UpdateNeighborLinkFailures(*neighbor, aError, /* aAllowNeighborRemove */ true, failLimit);
exit:
return neighbor;
}
void MeshForwarder::UpdateNeighborLinkFailures(Neighbor &aNeighbor,
Error aError,
bool aAllowNeighborRemove,
uint8_t aFailLimit)
{
// Update neighbor `LinkFailures` counter on ack error.
if (aError == kErrorNone)
{
aNeighbor.ResetLinkFailures();
}
else if (aError == kErrorNoAck)
{
aNeighbor.IncrementLinkFailures();
if (aAllowNeighborRemove && (Mle::IsActiveRouter(aNeighbor.GetRloc16())) &&
(aNeighbor.GetLinkFailures() >= aFailLimit))
{
Get<Mle::MleRouter>().RemoveRouterLink(static_cast<Router &>(aNeighbor));
}
}
}
#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
void MeshForwarder::HandleDeferredAck(Neighbor &aNeighbor, Error aError)
{
bool allowNeighborRemove = true;
VerifyOrExit(mEnabled);
if (aError == kErrorNoAck)
{
LogInfo("Deferred ack timeout on trel for neighbor %s rloc16:0x%04x",
aNeighbor.GetExtAddress().ToString().AsCString(), aNeighbor.GetRloc16());
}
#if OPENTHREAD_CONFIG_MULTI_RADIO
// In multi radio mode, `RadioSelector` will update the neighbor's
// link failure counter and removes the neighbor if required.
Get<RadioSelector>().UpdateOnDeferredAck(aNeighbor, aError, allowNeighborRemove);
#else
UpdateNeighborLinkFailures(aNeighbor, aError, allowNeighborRemove, kFailedRouterTransmissions);
#endif
exit:
return;
}
#endif // #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
void MeshForwarder::HandleSentFrame(Mac::TxFrame &aFrame, Error aError)
{
Neighbor *neighbor = nullptr;
Mac::Address macDest;
OT_ASSERT((aError == kErrorNone) || (aError == kErrorChannelAccessFailure) || (aError == kErrorAbort) ||
(aError == kErrorNoAck));
mSendBusy = false;
VerifyOrExit(mEnabled);
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_COLLISION_AVOIDANCE_DELAY_ENABLE
if (mDelayNextTx && (aError == kErrorNone))
{
mTxDelayTimer.Start(kTxDelayInterval);
LogDebg("Start tx delay timer for %lu msec", ToUlong(kTxDelayInterval));
}
else
{
mDelayNextTx = false;
}
#endif
if (!aFrame.IsEmpty())
{
IgnoreError(aFrame.GetDstAddr(macDest));
neighbor = UpdateNeighborOnSentFrame(aFrame, aError, macDest, /* aIsDataPoll */ false);
}
UpdateSendMessage(aError, macDest, neighbor);
exit:
return;
}
void MeshForwarder::UpdateSendMessage(Error aFrameTxError, Mac::Address &aMacDest, Neighbor *aNeighbor)
{
Error txError = aFrameTxError;
VerifyOrExit(mSendMessage != nullptr);
OT_ASSERT(mSendMessage->IsDirectTransmission());
if (aFrameTxError != kErrorNone)
{
// If the transmission of any fragment frame fails,
// the overall message transmission is considered
// as failed
mSendMessage->SetTxSuccess(false);
#if OPENTHREAD_CONFIG_DROP_MESSAGE_ON_FRAGMENT_TX_FAILURE
// We set the NextOffset to end of message to avoid sending
// any remaining fragments in the message.
mMessageNextOffset = mSendMessage->GetLength();
#endif
}
if (mMessageNextOffset < mSendMessage->GetLength())
{
mSendMessage->SetOffset(mMessageNextOffset);
ExitNow();
}
txError = aFrameTxError;
mSendMessage->ClearDirectTransmission();
mSendMessage->SetOffset(0);
if (aNeighbor != nullptr)
{
aNeighbor->GetLinkInfo().AddMessageTxStatus(mSendMessage->GetTxSuccess());
}
#if !OPENTHREAD_CONFIG_DROP_MESSAGE_ON_FRAGMENT_TX_FAILURE
// When `CONFIG_DROP_MESSAGE_ON_FRAGMENT_TX_FAILURE` is
// disabled, all fragment frames of a larger message are
// sent even if the transmission of an earlier fragment fail.
// Note that `GetTxSuccess() tracks the tx success of the
// entire message, while `aFrameTxError` represents the error
// status of the last fragment frame transmission.
if (!mSendMessage->GetTxSuccess() && (txError == kErrorNone))
{
txError = kErrorFailed;
}
#endif
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
Get<Utils::HistoryTracker>().RecordTxMessage(*mSendMessage, aMacDest);
#endif
LogMessage(kMessageTransmit, *mSendMessage, txError, &aMacDest);
if (mSendMessage->GetType() == Message::kTypeIp6)
{
if (mSendMessage->GetTxSuccess())
{
mIpCounters.mTxSuccess++;
}
else
{
mIpCounters.mTxFailure++;
}
}
switch (mSendMessage->GetSubType())
{
case Message::kSubTypeMleDiscoverRequest:
// Note that `HandleDiscoveryRequestFrameTxDone()` may update
// `mSendMessage` and mark it again for direct transmission.
Get<Mle::DiscoverScanner>().HandleDiscoveryRequestFrameTxDone(*mSendMessage);
break;
case Message::kSubTypeMleChildIdRequest:
Get<Mle::Mle>().HandleChildIdRequestTxDone(*mSendMessage);
break;
default:
break;
}
RemoveMessageIfNoPendingTx(*mSendMessage);
exit:
mScheduleTransmissionTask.Post();
}
bool MeshForwarder::RemoveMessageIfNoPendingTx(Message &aMessage)
{
bool didRemove = false;
#if OPENTHREAD_FTD
VerifyOrExit(!aMessage.IsDirectTransmission() && !aMessage.IsChildPending());
#else
VerifyOrExit(!aMessage.IsDirectTransmission());
#endif
if (mSendMessage == &aMessage)
{
mSendMessage = nullptr;
mMessageNextOffset = 0;
}
mSendQueue.DequeueAndFree(aMessage);
didRemove = true;
exit:
return didRemove;
}
void MeshForwarder::HandleReceivedFrame(Mac::RxFrame &aFrame)
{
ThreadLinkInfo linkInfo;
Mac::Addresses macAddrs;
FrameData frameData;
Error error = kErrorNone;
VerifyOrExit(mEnabled, error = kErrorInvalidState);
SuccessOrExit(error = aFrame.GetSrcAddr(macAddrs.mSource));
SuccessOrExit(error = aFrame.GetDstAddr(macAddrs.mDestination));
linkInfo.SetFrom(aFrame);
frameData.Init(aFrame.GetPayload(), aFrame.GetPayloadLength());
Get<SupervisionListener>().UpdateOnReceive(macAddrs.mSource, linkInfo.IsLinkSecurityEnabled());
switch (aFrame.GetType())
{
case Mac::Frame::kTypeData:
if (Lowpan::MeshHeader::IsMeshHeader(frameData))
{
#if OPENTHREAD_FTD
HandleMesh(frameData, macAddrs.mSource, linkInfo);
#endif
}
else if (Lowpan::FragmentHeader::IsFragmentHeader(frameData))
{
HandleFragment(frameData, macAddrs, linkInfo);
}
else if (Lowpan::Lowpan::IsLowpanHc(frameData))
{
HandleLowpanHC(frameData, macAddrs, linkInfo);
}
else
{
VerifyOrExit(frameData.GetLength() == 0, error = kErrorNotLowpanDataFrame);
LogFrame("Received empty payload frame", aFrame, kErrorNone);
}
break;
case Mac::Frame::kTypeBeacon:
break;
default:
error = kErrorDrop;
break;
}
exit:
if (error != kErrorNone)
{
LogFrame("Dropping rx frame", aFrame, error);
}
}
void MeshForwarder::HandleFragment(FrameData &aFrameData,
const Mac::Addresses &aMacAddrs,
const ThreadLinkInfo &aLinkInfo)
{
Error error = kErrorNone;
Lowpan::FragmentHeader fragmentHeader;
Message *message = nullptr;
SuccessOrExit(error = fragmentHeader.ParseFrom(aFrameData));
#if OPENTHREAD_CONFIG_MULTI_RADIO
if (aLinkInfo.mLinkSecurity)
{
Neighbor *neighbor = Get<NeighborTable>().FindNeighbor(aMacAddrs.mSource, Neighbor::kInStateAnyExceptInvalid);
if (neighbor != nullptr)
{
uint16_t tag = fragmentHeader.GetDatagramTag();
if (neighbor->IsLastRxFragmentTagSet())
{
VerifyOrExit(!neighbor->IsLastRxFragmentTagAfter(tag), error = kErrorDuplicated);
if (neighbor->GetLastRxFragmentTag() == tag)
{
VerifyOrExit(fragmentHeader.GetDatagramOffset() != 0, error = kErrorDuplicated);
// Duplication suppression for a "next fragment" is handled
// by the code below where the the datagram offset is
// checked against the offset of the corresponding message
// (same datagram tag and size) in Reassembly List. Note
// that if there is no matching message in the Reassembly
// List (e.g., in case the message is already fully
// assembled) the received "next fragment" frame would be
// dropped.
}
}
neighbor->SetLastRxFragmentTag(tag);
}
}
#endif // OPENTHREAD_CONFIG_MULTI_RADIO
if (fragmentHeader.GetDatagramOffset() == 0)
{
uint16_t datagramSize = fragmentHeader.GetDatagramSize();
#if OPENTHREAD_FTD
UpdateRoutes(aFrameData, aMacAddrs);
#endif
SuccessOrExit(error = FrameToMessage(aFrameData, datagramSize, aMacAddrs, message));
VerifyOrExit(datagramSize >= message->GetLength(), error = kErrorParse);
SuccessOrExit(error = message->SetLength(datagramSize));
message->SetDatagramTag(fragmentHeader.GetDatagramTag());
message->SetTimestampToNow();
message->SetLinkInfo(aLinkInfo);
VerifyOrExit(Get<Ip6::Filter>().Accept(*message), error = kErrorDrop);
#if OPENTHREAD_FTD
SendIcmpErrorIfDstUnreach(*message, aMacAddrs);
#endif
// Allow re-assembly of only one message at a time on a SED by clearing
// any remaining fragments in reassembly list upon receiving of a new
// (secure) first fragment.
if (!GetRxOnWhenIdle() && message->IsLinkSecurityEnabled())
{
ClearReassemblyList();
}
mReassemblyList.Enqueue(*message);
Get<TimeTicker>().RegisterReceiver(TimeTicker::kMeshForwarder);
}
else // Received frame is a "next fragment".
{
for (Message &msg : mReassemblyList)
{
// Security Check: only consider reassembly buffers that had the same Security Enabled setting.
if (msg.GetLength() == fragmentHeader.GetDatagramSize() &&
msg.GetDatagramTag() == fragmentHeader.GetDatagramTag() &&
msg.GetOffset() == fragmentHeader.GetDatagramOffset() &&
msg.GetOffset() + aFrameData.GetLength() <= fragmentHeader.GetDatagramSize() &&
msg.IsLinkSecurityEnabled() == aLinkInfo.IsLinkSecurityEnabled())
{
message = &msg;
break;
}
}
// For a sleepy-end-device, if we receive a new (secure) next fragment
// with a non-matching fragmentation offset or tag, it indicates that
// we have either missed a fragment, or the parent has moved to a new
// message with a new tag. In either case, we can safely clear any
// remaining fragments stored in the reassembly list.
if (!GetRxOnWhenIdle() && (message == nullptr) && aLinkInfo.IsLinkSecurityEnabled())
{
ClearReassemblyList();
}
VerifyOrExit(message != nullptr, error = kErrorDrop);
message->WriteData(message->GetOffset(), aFrameData);
message->MoveOffset(aFrameData.GetLength());
message->AddRss(aLinkInfo.GetRss());
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
message->AddLqi(aLinkInfo.GetLqi());
#endif
message->SetTimestampToNow();
}
exit:
if (error == kErrorNone)
{
if (message->GetOffset() >= message->GetLength())
{
mReassemblyList.Dequeue(*message);
IgnoreError(HandleDatagram(*message, aLinkInfo, aMacAddrs.mSource));
}
}
else
{
LogFragmentFrameDrop(error, aFrameData.GetLength(), aMacAddrs, fragmentHeader,
aLinkInfo.IsLinkSecurityEnabled());
FreeMessage(message);
}
}
void MeshForwarder::ClearReassemblyList(void)
{
for (Message &message : mReassemblyList)
{
LogMessage(kMessageReassemblyDrop, message, kErrorNoFrameReceived);
if (message.GetType() == Message::kTypeIp6)
{
mIpCounters.mRxFailure++;
}
mReassemblyList.DequeueAndFree(message);
}
}
void MeshForwarder::HandleTimeTick(void)
{
bool continueRxingTicks = false;
#if OPENTHREAD_FTD
continueRxingTicks = mFragmentPriorityList.UpdateOnTimeTick();
#endif
continueRxingTicks = UpdateReassemblyList() || continueRxingTicks;
if (!continueRxingTicks)
{
Get<TimeTicker>().UnregisterReceiver(TimeTicker::kMeshForwarder);
}
}
bool MeshForwarder::UpdateReassemblyList(void)
{
TimeMilli now = TimerMilli::GetNow();
for (Message &message : mReassemblyList)
{
if (now - message.GetTimestamp() >= TimeMilli::SecToMsec(kReassemblyTimeout))
{
LogMessage(kMessageReassemblyDrop, message, kErrorReassemblyTimeout);
if (message.GetType() == Message::kTypeIp6)
{
mIpCounters.mRxFailure++;
}
mReassemblyList.DequeueAndFree(message);
}
}
return mReassemblyList.GetHead() != nullptr;
}
Error MeshForwarder::FrameToMessage(const FrameData &aFrameData,
uint16_t aDatagramSize,
const Mac::Addresses &aMacAddrs,
Message *&aMessage)
{
Error error = kErrorNone;
FrameData frameData = aFrameData;
Message::Priority priority;
SuccessOrExit(error = GetFramePriority(frameData, aMacAddrs, priority));
aMessage = Get<MessagePool>().Allocate(Message::kTypeIp6, /* aReserveHeader */ 0, Message::Settings(priority));
VerifyOrExit(aMessage, error = kErrorNoBufs);
SuccessOrExit(error = Get<Lowpan::Lowpan>().Decompress(*aMessage, aMacAddrs, frameData, aDatagramSize));
SuccessOrExit(error = aMessage->AppendData(frameData));
aMessage->MoveOffset(frameData.GetLength());
exit:
return error;
}
void MeshForwarder::HandleLowpanHC(const FrameData &aFrameData,
const Mac::Addresses &aMacAddrs,
const ThreadLinkInfo &aLinkInfo)
{
Error error = kErrorNone;
Message *message = nullptr;
#if OPENTHREAD_FTD
UpdateRoutes(aFrameData, aMacAddrs);
#endif
SuccessOrExit(error = FrameToMessage(aFrameData, 0, aMacAddrs, message));
message->SetLinkInfo(aLinkInfo);
VerifyOrExit(Get<Ip6::Filter>().Accept(*message), error = kErrorDrop);
#if OPENTHREAD_FTD
SendIcmpErrorIfDstUnreach(*message, aMacAddrs);
#endif
exit:
if (error == kErrorNone)
{
IgnoreError(HandleDatagram(*message, aLinkInfo, aMacAddrs.mSource));
}
else
{
LogLowpanHcFrameDrop(error, aFrameData.GetLength(), aMacAddrs, aLinkInfo.IsLinkSecurityEnabled());
FreeMessage(message);
}
}
Error MeshForwarder::HandleDatagram(Message &aMessage, const ThreadLinkInfo &aLinkInfo, const Mac::Address &aMacSource)
{
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
Get<Utils::HistoryTracker>().RecordRxMessage(aMessage, aMacSource);
#endif
LogMessage(kMessageReceive, aMessage, kErrorNone, &aMacSource);
if (aMessage.GetType() == Message::kTypeIp6)
{
mIpCounters.mRxSuccess++;
}
aMessage.SetLoopbackToHostAllowed(true);
aMessage.SetOrigin(Message::kOriginThreadNetif);
return Get<Ip6::Ip6>().HandleDatagram(aMessage, &aLinkInfo);
}
Error MeshForwarder::GetFramePriority(const FrameData &aFrameData,
const Mac::Addresses &aMacAddrs,
Message::Priority &aPriority)
{
Error error = kErrorNone;
Ip6::Headers headers;
SuccessOrExit(error = headers.DecompressFrom(aFrameData, aMacAddrs, GetInstance()));
aPriority = Ip6::Ip6::DscpToPriority(headers.GetIp6Header().GetDscp());
// Only ICMPv6 error messages are prioritized.
if (headers.IsIcmp6() && headers.GetIcmpHeader().IsError())
{
aPriority = Message::kPriorityNet;
}
if (headers.IsUdp())
{
uint16_t destPort = headers.GetUdpHeader().GetDestinationPort();
if (destPort == Mle::kUdpPort)
{
aPriority = Message::kPriorityNet;
}
else if (Get<Tmf::Agent>().IsTmfMessage(headers.GetSourceAddress(), headers.GetDestinationAddress(), destPort))
{
aPriority = Tmf::Agent::DscpToPriority(headers.GetIp6Header().GetDscp());
}
}
exit:
return error;
}
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
Error MeshForwarder::SendEmptyMessage(void)
{
Error error = kErrorNone;
Message *message = nullptr;
VerifyOrExit(mEnabled && !Get<Mac::Mac>().GetRxOnWhenIdle() &&
Get<Mle::MleRouter>().GetParent().IsStateValidOrRestoring(),
error = kErrorInvalidState);
message = Get<MessagePool>().Allocate(Message::kTypeMacEmptyData);
VerifyOrExit(message != nullptr, error = kErrorNoBufs);
SuccessOrExit(error = SendMessage(*message));
exit:
FreeMessageOnError(message, error);
LogDebg("Send empty message, error:%s", ErrorToString(error));
return error;
}
#endif // OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
bool MeshForwarder::CalcIePresent(const Message *aMessage)
{
bool iePresent = false;
OT_UNUSED_VARIABLE(aMessage);
#if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
iePresent |= (aMessage != nullptr && aMessage->IsTimeSync());
#endif
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
if (!(aMessage != nullptr && aMessage->GetSubType() == Message::kSubTypeMleDiscoverRequest))
{
iePresent |= Get<Mac::Mac>().IsCslEnabled();
}
#endif
#endif
return iePresent;
}
#if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT
void MeshForwarder::AppendHeaderIe(const Message *aMessage, Mac::TxFrame &aFrame)
{
uint8_t index = 0;
bool iePresent = false;
bool payloadPresent =
(aFrame.GetType() == Mac::Frame::kTypeMacCmd) || (aMessage != nullptr && aMessage->GetLength() != 0);
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
if (aMessage != nullptr && aMessage->IsTimeSync())
{
IgnoreError(aFrame.AppendHeaderIeAt<Mac::TimeIe>(index));
iePresent = true;
}
#endif
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
if (Get<Mac::Mac>().IsCslEnabled())
{
IgnoreError(aFrame.AppendHeaderIeAt<Mac::CslIe>(index));
aFrame.SetCslIePresent(true);
iePresent = true;
}
#endif
if (iePresent && payloadPresent)
{
// Assume no Payload IE in current implementation
IgnoreError(aFrame.AppendHeaderIeAt<Mac::Termination2Ie>(index));
}
}
#endif
Mac::Frame::Version MeshForwarder::CalcFrameVersion(const Neighbor *aNeighbor, bool aIePresent) const
{
Mac::Frame::Version version = Mac::Frame::kVersion2006;
OT_UNUSED_VARIABLE(aNeighbor);
if (aIePresent)
{
version = Mac::Frame::kVersion2015;
}
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
else if ((aNeighbor != nullptr) && Get<ChildTable>().Contains(*aNeighbor) &&
static_cast<const Child *>(aNeighbor)->IsCslSynchronized())
{
version = Mac::Frame::kVersion2015;
}
#endif
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE
else if (aNeighbor != nullptr && aNeighbor->IsEnhAckProbingActive())
{
version = Mac::Frame::kVersion2015; ///< Set version to 2015 to fetch Link Metrics data in Enh-ACK.
}
#endif
return version;
}
// LCOV_EXCL_START
#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_NOTE)
const char *MeshForwarder::MessageActionToString(MessageAction aAction, Error aError)
{
static const char *const kMessageActionStrings[] = {
"Received", // (0) kMessageReceive
"Sent", // (1) kMessageTransmit
"Prepping indir tx", // (2) kMessagePrepareIndirect
"Dropping", // (3) kMessageDrop
"Dropping (reassembly queue)", // (4) kMessageReassemblyDrop
"Evicting", // (5) kMessageEvict
#if OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_ENABLE
"Marked ECN", // (6) kMessageMarkEcn
"Dropping (queue mgmt)", // (7) kMessageQueueMgmtDrop
#endif
#if (OPENTHREAD_CONFIG_MAX_FRAMES_IN_DIRECT_TX_QUEUE > 0)
"Dropping (dir queue full)", // (8) kMessageFullQueueDrop
#endif
};
const char *string = kMessageActionStrings[aAction];
static_assert(kMessageReceive == 0, "kMessageReceive value is incorrect");
static_assert(kMessageTransmit == 1, "kMessageTransmit value is incorrect");
static_assert(kMessagePrepareIndirect == 2, "kMessagePrepareIndirect value is incorrect");
static_assert(kMessageDrop == 3, "kMessageDrop value is incorrect");
static_assert(kMessageReassemblyDrop == 4, "kMessageReassemblyDrop value is incorrect");
static_assert(kMessageEvict == 5, "kMessageEvict value is incorrect");
#if OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_ENABLE
static_assert(kMessageMarkEcn == 6, "kMessageMarkEcn is incorrect");
static_assert(kMessageQueueMgmtDrop == 7, "kMessageQueueMgmtDrop is incorrect");
#if (OPENTHREAD_CONFIG_MAX_FRAMES_IN_DIRECT_TX_QUEUE > 0)
static_assert(kMessageFullQueueDrop == 8, "kMessageFullQueueDrop is incorrect");
#endif
#else
#if (OPENTHREAD_CONFIG_MAX_FRAMES_IN_DIRECT_TX_QUEUE > 0)
static_assert(kMessageFullQueueDrop == 6, "kMessageFullQueueDrop is incorrect");
#endif
#endif
if ((aAction == kMessageTransmit) && (aError != kErrorNone))
{
string = "Failed to send";
}
return string;
}
const char *MeshForwarder::MessagePriorityToString(const Message &aMessage)
{
return Message::PriorityToString(aMessage.GetPriority());
}
#if OPENTHREAD_CONFIG_LOG_SRC_DST_IP_ADDRESSES
void MeshForwarder::LogIp6SourceDestAddresses(const Ip6::Headers &aHeaders, LogLevel aLogLevel)
{
uint16_t srcPort = aHeaders.GetSourcePort();
uint16_t dstPort = aHeaders.GetDestinationPort();
if (srcPort != 0)
{
LogAt(aLogLevel, " src:[%s]:%d", aHeaders.GetSourceAddress().ToString().AsCString(), srcPort);
}
else
{
LogAt(aLogLevel, " src:[%s]", aHeaders.GetSourceAddress().ToString().AsCString());
}
if (dstPort != 0)
{
LogAt(aLogLevel, " dst:[%s]:%d", aHeaders.GetDestinationAddress().ToString().AsCString(), dstPort);
}
else
{
LogAt(aLogLevel, " dst:[%s]", aHeaders.GetDestinationAddress().ToString().AsCString());
}
}
#else
void MeshForwarder::LogIp6SourceDestAddresses(const Ip6::Headers &, LogLevel) {}
#endif
void MeshForwarder::LogIp6Message(MessageAction aAction,
const Message &aMessage,
const Mac::Address *aMacAddress,
Error aError,
LogLevel aLogLevel)
{
Ip6::Headers headers;
bool shouldLogRss;
bool shouldLogRadio = false;
const char *radioString = "";
SuccessOrExit(headers.ParseFrom(aMessage));
shouldLogRss = (aAction == kMessageReceive) || (aAction == kMessageReassemblyDrop);
#if OPENTHREAD_CONFIG_MULTI_RADIO
shouldLogRadio = true;
radioString = aMessage.IsRadioTypeSet() ? RadioTypeToString(aMessage.GetRadioType()) : "all";
#endif
LogAt(aLogLevel, "%s IPv6 %s msg, len:%d, chksum:%04x, ecn:%s%s%s, sec:%s%s%s, prio:%s%s%s%s%s",
MessageActionToString(aAction, aError), Ip6::Ip6::IpProtoToString(headers.GetIpProto()), aMessage.GetLength(),
headers.GetChecksum(), Ip6::Ip6::EcnToString(headers.GetEcn()),
(aMacAddress == nullptr) ? "" : ((aAction == kMessageReceive) ? ", from:" : ", to:"),
(aMacAddress == nullptr) ? "" : aMacAddress->ToString().AsCString(),
ToYesNo(aMessage.IsLinkSecurityEnabled()),
(aError == kErrorNone) ? "" : ", error:", (aError == kErrorNone) ? "" : ErrorToString(aError),
MessagePriorityToString(aMessage), shouldLogRss ? ", rss:" : "",
shouldLogRss ? aMessage.GetRssAverager().ToString().AsCString() : "", shouldLogRadio ? ", radio:" : "",
radioString);
if (aAction != kMessagePrepareIndirect)
{
LogIp6SourceDestAddresses(headers, aLogLevel);
}
exit:
return;
}
void MeshForwarder::LogMessage(MessageAction aAction, const Message &aMessage)
{
LogMessage(aAction, aMessage, kErrorNone);
}
void MeshForwarder::LogMessage(MessageAction aAction, const Message &aMessage, Error aError)
{
LogMessage(aAction, aMessage, aError, nullptr);
}
void MeshForwarder::LogMessage(MessageAction aAction,
const Message &aMessage,
Error aError,
const Mac::Address *aMacAddress)
{
LogLevel logLevel = kLogLevelInfo;
switch (aAction)
{
case kMessageReceive:
case kMessageTransmit:
case kMessagePrepareIndirect:
#if OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_ENABLE
case kMessageMarkEcn:
#endif
logLevel = (aError == kErrorNone) ? kLogLevelInfo : kLogLevelNote;
break;
case kMessageDrop:
case kMessageReassemblyDrop:
case kMessageEvict:
#if OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_ENABLE
case kMessageQueueMgmtDrop:
#endif
#if (OPENTHREAD_CONFIG_MAX_FRAMES_IN_DIRECT_TX_QUEUE > 0)
case kMessageFullQueueDrop:
#endif
logLevel = kLogLevelNote;
break;
}
VerifyOrExit(Instance::GetLogLevel() >= logLevel);
switch (aMessage.GetType())
{
case Message::kTypeIp6:
LogIp6Message(aAction, aMessage, aMacAddress, aError, logLevel);
break;
#if OPENTHREAD_FTD
case Message::kType6lowpan:
LogMeshMessage(aAction, aMessage, aMacAddress, aError, logLevel);
break;
#endif
default:
break;
}
exit:
return;
}
void MeshForwarder::LogFrame(const char *aActionText, const Mac::Frame &aFrame, Error aError)
{
if (aError != kErrorNone)
{
LogNote("%s, aError:%s, %s", aActionText, ErrorToString(aError), aFrame.ToInfoString().AsCString());
}
else
{
LogInfo("%s, %s", aActionText, aFrame.ToInfoString().AsCString());
}
}
void MeshForwarder::LogFragmentFrameDrop(Error aError,
uint16_t aFrameLength,
const Mac::Addresses &aMacAddrs,
const Lowpan::FragmentHeader &aFragmentHeader,
bool aIsSecure)
{
LogNote("Dropping rx frag frame, error:%s, len:%d, src:%s, dst:%s, tag:%d, offset:%d, dglen:%d, sec:%s",
ErrorToString(aError), aFrameLength, aMacAddrs.mSource.ToString().AsCString(),
aMacAddrs.mDestination.ToString().AsCString(), aFragmentHeader.GetDatagramTag(),
aFragmentHeader.GetDatagramOffset(), aFragmentHeader.GetDatagramSize(), ToYesNo(aIsSecure));
}
void MeshForwarder::LogLowpanHcFrameDrop(Error aError,
uint16_t aFrameLength,
const Mac::Addresses &aMacAddrs,
bool aIsSecure)
{
LogNote("Dropping rx lowpan HC frame, error:%s, len:%d, src:%s, dst:%s, sec:%s", ErrorToString(aError),
aFrameLength, aMacAddrs.mSource.ToString().AsCString(), aMacAddrs.mDestination.ToString().AsCString(),
ToYesNo(aIsSecure));
}
#else // #if OT_SHOULD_LOG_AT( OT_LOG_LEVEL_NOTE)
void MeshForwarder::LogMessage(MessageAction, const Message &) {}
void MeshForwarder::LogMessage(MessageAction, const Message &, Error) {}
void MeshForwarder::LogMessage(MessageAction, const Message &, Error, const Mac::Address *) {}
void MeshForwarder::LogFrame(const char *, const Mac::Frame &, Error) {}
void MeshForwarder::LogFragmentFrameDrop(Error, uint16_t, const Mac::Addresses &, const Lowpan::FragmentHeader &, bool)
{
}
void MeshForwarder::LogLowpanHcFrameDrop(Error, uint16_t, const Mac::Addresses &, bool) {}
#endif // #if OT_SHOULD_LOG_AT( OT_LOG_LEVEL_NOTE)
// LCOV_EXCL_STOP
} // namespace ot