diff --git a/src/core/config/misc.h b/src/core/config/misc.h index 8909c4559..3070bb07c 100644 --- a/src/core/config/misc.h +++ b/src/core/config/misc.h @@ -483,6 +483,22 @@ #define OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_FRAG_TAG_ENTRY_LIST_SIZE 16 #endif +/** + * @def OPENTHREAD_CONFIG_MAX_FRAMES_IN_DIRECT_TX_QUEUE + * + * Specifies the maximum number of frames in direct tx queue before new direct tx messages are dropped. + * + * If set to zero then the behavior is disabled, i.e., no check is performed on tx queue length. + * + */ +#ifndef OPENTHREAD_CONFIG_MAX_FRAMES_IN_DIRECT_TX_QUEUE +#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_3) +#define OPENTHREAD_CONFIG_MAX_FRAMES_IN_DIRECT_TX_QUEUE 100 +#else +#define OPENTHREAD_CONFIG_MAX_FRAMES_IN_DIRECT_TX_QUEUE 0 +#endif +#endif + /** * @def OPENTHREAD_CONFIG_PLATFORM_RADIO_PROPRIETARY_SUPPORT * diff --git a/src/core/thread/mesh_forwarder.cpp b/src/core/thread/mesh_forwarder.cpp index c7f68339b..bd55d74d6 100644 --- a/src/core/thread/mesh_forwarder.cpp +++ b/src/core/thread/mesh_forwarder.cpp @@ -438,6 +438,89 @@ Error MeshForwarder::RemoveAgedMessages(void) #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 + if (RemoveAgedMessages() == kErrorNone) + { + VerifyOrExit(IsDirectTxQueueOverMaxFrameThreshold()); + } +#endif + + LogMessage(kMessageFullQueueDrop, aMessage); + aMessage.ClearDirectTransmission(); + RemoveMessageIfNoPendingTx(aMessage); + +exit: + return; +} + +#endif // (OPENTHREAD_CONFIG_MAX_FRAMES_IN_DIRECT_TX_QUEUE > 0) + void MeshForwarder::ScheduleTransmissionTask(Tasklet &aTasklet) { aTasklet.Get().ScheduleTransmissionTask(); @@ -1809,6 +1892,9 @@ const char *MeshForwarder::MessageActionToString(MessageAction aAction, Error aE #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 }; @@ -1823,6 +1909,13 @@ const char *MeshForwarder::MessageActionToString(MessageAction aAction, Error aE #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)) @@ -1932,6 +2025,9 @@ void MeshForwarder::LogMessage(MessageAction aAction, 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; diff --git a/src/core/thread/mesh_forwarder.hpp b/src/core/thread/mesh_forwarder.hpp index be4cb6217..e063c9dfb 100644 --- a/src/core/thread/mesh_forwarder.hpp +++ b/src/core/thread/mesh_forwarder.hpp @@ -352,6 +352,9 @@ private: #if OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_ENABLE kMessageMarkEcn, // Indicates that ECN is marked on an outbound message by delay-aware queue management. kMessageQueueMgmtDrop, // Indicates that an outbound message is dropped by delay-aware queue management. +#endif +#if (OPENTHREAD_CONFIG_MAX_FRAMES_IN_DIRECT_TX_QUEUE > 0) + kMessageFullQueueDrop, // Indicates message drop due to reaching max allowed frames in direct tx queue. #endif }; @@ -460,6 +463,10 @@ private: #if OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_ENABLE Error UpdateEcnOrDrop(Message &aMessage, bool aPreparingToSend = true); Error RemoveAgedMessages(void); +#endif +#if (OPENTHREAD_CONFIG_MAX_FRAMES_IN_DIRECT_TX_QUEUE > 0) + bool IsDirectTxQueueOverMaxFrameThreshold(void) const; + void ApplyDirectTxQueueLimit(Message &aMessage); #endif void SendMesh(Message &aMessage, Mac::TxFrame &aFrame); void SendDestinationUnreachable(uint16_t aMeshSource, const Ip6::Headers &aIp6Headers); diff --git a/src/core/thread/mesh_forwarder_ftd.cpp b/src/core/thread/mesh_forwarder_ftd.cpp index 25688e297..9b5cdc33a 100644 --- a/src/core/thread/mesh_forwarder_ftd.cpp +++ b/src/core/thread/mesh_forwarder_ftd.cpp @@ -134,6 +134,10 @@ Error MeshForwarder::SendMessage(Message &aMessage) break; } +#if (OPENTHREAD_CONFIG_MAX_FRAMES_IN_DIRECT_TX_QUEUE > 0) + ApplyDirectTxQueueLimit(aMessage); +#endif + mScheduleTransmissionTask.Post(); return error; diff --git a/src/core/thread/mesh_forwarder_mtd.cpp b/src/core/thread/mesh_forwarder_mtd.cpp index d74fba23f..5e697056b 100644 --- a/src/core/thread/mesh_forwarder_mtd.cpp +++ b/src/core/thread/mesh_forwarder_mtd.cpp @@ -47,6 +47,10 @@ Error MeshForwarder::SendMessage(Message &aMessage) mSendQueue.Enqueue(aMessage); mScheduleTransmissionTask.Post(); +#if (OPENTHREAD_CONFIG_MAX_FRAMES_IN_DIRECT_TX_QUEUE > 0) + ApplyDirectTxQueueLimit(aMessage); +#endif + return kErrorNone; }