[mesh-forwarder] add delay-aware queue management (#7568)
This commit implements delay-aware queue management. When enabled the device will monitor time-in-queue of messages in the direct tx queue and if it is lager than specified thresholds it updates ECN flag (if message indicates it is ECN-capable) and/or drop the message. This mechanism is applied to IPv6 messages on the first device that sends the message into Thread mesh and also on intermediate routers that are forwarding the message (e.g., as a "mesh lowpan fragment" frame). On an intermediate router when forwarding the fragments of a message, if any fragment is dropped by the queue management policy, all subsequent fragments will also be dropped. In particular, this commit contains the following: - Adds `DecompressEcn()` and `MarkCompressedEcn()` in `Lowpan` class to decompress or update the ECN field in a compressed IPHC header (unit test `test_lowpan` is also updated to test the new methods). - Adds `UpdateEcnOrDrop()` which implements the main queue management logic. This method is used when preparing next direct tx message. It decides whether to keep the message as is, update ECN on it or drop it. - Updates `EvictMessage()` to first apply the queue management rule to see if any message can be dropped before using the eviction logic based on message priority. - Updates and reuses the `FragmentPriorityList` to track whether queue management dropped any of the fragments of same message so to also drop any subsequent ones. - Updates `LogMessage()` to log when a message is dropped by queue-management or when ECN is marked on a message.
This commit is contained in:
parent
8aca0655e7
commit
2ce3d3bf02
|
@ -421,6 +421,68 @@
|
|||
#define OPENTHREAD_CONFIG_NUM_FRAGMENT_PRIORITY_ENTRIES 8
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_ENABLE
|
||||
*
|
||||
* Define to 1 to enable delay-aware queue management for the send queue.
|
||||
*
|
||||
* When enabled device will monitor time-in-queue of messages in the direct tx queue and if the wait time is lager than
|
||||
* specified thresholds it may update ECN flag (if message indicates it is ECN-capable) or drop the message.
|
||||
*
|
||||
*/
|
||||
#ifndef OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_ENABLE
|
||||
#define OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_ENABLE \
|
||||
(OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_3)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_MARK_ECN_INTERVAL
|
||||
*
|
||||
* Specifies the time-in-queue threshold interval in milliseconds to mark ECN on a message if it is ECN-capable or
|
||||
* drop the message if not ECN-capable.
|
||||
*
|
||||
*/
|
||||
#ifndef OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_MARK_ECN_INTERVAL
|
||||
#define OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_MARK_ECN_INTERVAL 500
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_DROP_MSG_INTERVAL
|
||||
*
|
||||
* Specifies the time-in-queue threshold interval in milliseconds to drop a message.
|
||||
*
|
||||
*/
|
||||
#ifndef OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_DROP_MSG_INTERVAL
|
||||
#define OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_DROP_MSG_INTERVAL 1000
|
||||
#endif
|
||||
|
||||
/**
|
||||
* OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_FRAG_TAG_RETAIN_TIME
|
||||
*
|
||||
* Specifies the max retain time in seconds of a mesh header fragmentation tag entry in the list.
|
||||
*
|
||||
* The entry in list is used to track whether an earlier fragment of same message was dropped by the router and if so
|
||||
* the next fragments are also dropped. The entry is removed once last fragment is processed or after the retain time
|
||||
* specified by this config parameter expires.
|
||||
*
|
||||
*/
|
||||
#ifndef OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_FRAG_TAG_RETAIN_TIME
|
||||
#define OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_FRAG_TAG_RETAIN_TIME (4 * 60) // 4 minutes
|
||||
#endif
|
||||
|
||||
/**
|
||||
* OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_FRAG_TAG_ENTRY_LIST_SIZE
|
||||
*
|
||||
* Specifies the number of mesh header fragmentation tag entries in the list for delay-aware queue management.
|
||||
*
|
||||
* The list is used to track whether an earlier fragment of same message was dropped by the router and if so the next
|
||||
* fragments are also dropped.
|
||||
*
|
||||
*/
|
||||
#ifndef OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_FRAG_TAG_ENTRY_LIST_SIZE
|
||||
#define OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_FRAG_TAG_ENTRY_LIST_SIZE 16
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_PLATFORM_RADIO_PROPRIETARY_SUPPORT
|
||||
*
|
||||
|
|
|
@ -213,6 +213,7 @@ void DiscoverScanner::HandleDiscoveryRequestFrameTxDone(Message &aMessage)
|
|||
// the next scan channel. Also pause message tx on `MeshForwarder`
|
||||
// while listening to receive Discovery Responses.
|
||||
aMessage.SetDirectTransmission();
|
||||
aMessage.SetTimestampToNow();
|
||||
Get<MeshForwarder>().PauseMessageTransmissions();
|
||||
mTimer.Start(kDefaultScanDuration);
|
||||
break;
|
||||
|
|
|
@ -1179,6 +1179,46 @@ exit:
|
|||
return (error == kErrorNone) ? static_cast<int>(compressedLength) : -1;
|
||||
}
|
||||
|
||||
Ip6::Ecn Lowpan::DecompressEcn(const Message &aMessage, uint16_t aOffset) const
|
||||
{
|
||||
Ip6::Ecn ecn = Ip6::kEcnNotCapable;
|
||||
uint16_t hcCtl;
|
||||
uint8_t byte;
|
||||
|
||||
SuccessOrExit(aMessage.Read(aOffset, hcCtl));
|
||||
hcCtl = HostSwap16(hcCtl);
|
||||
|
||||
VerifyOrExit((hcCtl & kHcDispatchMask) == kHcDispatch);
|
||||
aOffset += sizeof(uint16_t);
|
||||
|
||||
if ((hcCtl & kHcTrafficFlowMask) == kHcTrafficFlow)
|
||||
{
|
||||
// ECN is elided and is zero (`kEcnNotCapable`).
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
// When ECN is not elided, it is always included as the
|
||||
// first two bits of the next byte.
|
||||
SuccessOrExit(aMessage.Read(aOffset, byte));
|
||||
ecn = static_cast<Ip6::Ecn>((byte & kEcnMask) >> kEcnOffset);
|
||||
|
||||
exit:
|
||||
return ecn;
|
||||
}
|
||||
|
||||
void Lowpan::MarkCompressedEcn(Message &aMessage, uint16_t aOffset)
|
||||
{
|
||||
uint8_t byte;
|
||||
|
||||
aOffset += sizeof(uint16_t);
|
||||
IgnoreError(aMessage.Read(aOffset, byte));
|
||||
|
||||
byte &= ~kEcnMask;
|
||||
byte |= static_cast<uint8_t>(Ip6::kEcnMarked << kEcnOffset);
|
||||
|
||||
aMessage.Write(aOffset, byte);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------------
|
||||
// MeshHeader
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
#include "mac/mac_types.hpp"
|
||||
#include "net/ip6.hpp"
|
||||
#include "net/ip6_address.hpp"
|
||||
#include "net/ip6_types.hpp"
|
||||
|
||||
namespace ot {
|
||||
|
||||
|
@ -308,6 +309,29 @@ public:
|
|||
*/
|
||||
int DecompressUdpHeader(Ip6::Udp::Header &aUdpHeader, const uint8_t *aBuf, uint16_t aBufLength);
|
||||
|
||||
/**
|
||||
* This method decompresses the IPv6 ECN field in a LOWPAN_IPHC header.
|
||||
*
|
||||
* @param[in] aMessage The message to read the IPHC header from.
|
||||
* @param[in] aOffset The offset in @p aMessage to start of IPHC header.
|
||||
*
|
||||
* @returns The decompressed ECN field. If the IPHC header is not valid `kEcnNotCapable` is returned.
|
||||
*
|
||||
*/
|
||||
Ip6::Ecn DecompressEcn(const Message &aMessage, uint16_t aOffset) const;
|
||||
|
||||
/**
|
||||
* This method updates the compressed ECN field in a LOWPAN_IPHC header to `kEcnMarked`.
|
||||
*
|
||||
* This method MUST be used when the ECN field is not elided in the IPHC header. Note that the ECN is not elided
|
||||
* when it is not zero (`kEcnNotCapable`).
|
||||
*
|
||||
* @param[in,out] aMessage The message containing the IPHC header and to update.
|
||||
* @param[in] aOffset The offset in @p aMessage to start of IPHC header.
|
||||
*
|
||||
*/
|
||||
void MarkCompressedEcn(Message &aMessage, uint16_t aOffset);
|
||||
|
||||
private:
|
||||
static constexpr uint16_t kHcDispatch = 3 << 13;
|
||||
static constexpr uint16_t kHcDispatchMask = 7 << 13;
|
||||
|
@ -336,6 +360,9 @@ private:
|
|||
static constexpr uint16_t kHcDstAddrMode3 = 3 << 0;
|
||||
static constexpr uint16_t kHcDstAddrModeMask = 3 << 0;
|
||||
|
||||
static constexpr uint8_t kEcnOffset = 6;
|
||||
static constexpr uint8_t kEcnMask = 3 << kEcnOffset;
|
||||
|
||||
static constexpr uint8_t kExtHdrDispatch = 0xe0;
|
||||
static constexpr uint8_t kExtHdrDispatchMask = 0xf0;
|
||||
|
||||
|
|
|
@ -260,6 +260,184 @@ void MeshForwarder::HandleTxDelayTimer(void)
|
|||
}
|
||||
#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)
|
||||
{
|
||||
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())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (UpdateEcnOrDrop(*message, /* aPreparingToSend */ false) == kErrorDrop)
|
||||
{
|
||||
error = kErrorNone;
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
#endif // OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_ENABLE
|
||||
|
||||
void MeshForwarder::ScheduleTransmissionTask(Tasklet &aTasklet)
|
||||
{
|
||||
aTasklet.Get<MeshForwarder>().ScheduleTransmissionTask();
|
||||
|
@ -294,12 +472,24 @@ Message *MeshForwarder::PrepareNextDirectTransmission(void)
|
|||
|
||||
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())
|
||||
{
|
||||
nextMessage = curMessage->GetNext();
|
||||
continue;
|
||||
}
|
||||
|
||||
#if OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_ENABLE
|
||||
if (UpdateEcnOrDrop(*curMessage) == kErrorDrop)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
curMessage->SetDoNotEvict(true);
|
||||
|
||||
switch (curMessage->GetType())
|
||||
|
@ -1634,6 +1824,10 @@ const char *MeshForwarder::MessageActionToString(MessageAction aAction, Error aE
|
|||
"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
|
||||
};
|
||||
|
||||
const char *string = kMessageActionStrings[aAction];
|
||||
|
@ -1644,6 +1838,10 @@ const char *MeshForwarder::MessageActionToString(MessageAction aAction, Error aE
|
|||
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");
|
||||
#endif
|
||||
|
||||
if ((aAction == kMessageTransmit) && (aError != kErrorNone))
|
||||
{
|
||||
|
@ -1741,12 +1939,18 @@ void MeshForwarder::LogMessage(MessageAction 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
|
||||
logLevel = kLogLevelNote;
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -335,14 +335,23 @@ private:
|
|||
|
||||
static constexpr uint32_t kTxDelayInterval = OPENTHREAD_CONFIG_MAC_COLLISION_AVOIDANCE_DELAY_INTERVAL; // In msec
|
||||
|
||||
#if OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_ENABLE
|
||||
static constexpr uint32_t kTimeInQueueMarkEcn = OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_MARK_ECN_INTERVAL;
|
||||
static constexpr uint32_t kTimeInQueueDropMsg = OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_DROP_MSG_INTERVAL;
|
||||
#endif
|
||||
|
||||
enum MessageAction : uint8_t
|
||||
{
|
||||
kMessageReceive, // Indicates that the message was received.
|
||||
kMessageTransmit, // Indicates that the message was sent.
|
||||
kMessagePrepareIndirect, // Indicates that the message is being prepared for indirect tx.
|
||||
kMessageDrop, // Indicates that the outbound message is being dropped (e.g., dst unknown).
|
||||
kMessageDrop, // Indicates that the outbound message is dropped (e.g., dst unknown).
|
||||
kMessageReassemblyDrop, // Indicates that the message is being dropped from reassembly list.
|
||||
kMessageEvict, // Indicates that the message was evicted.
|
||||
#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
|
||||
};
|
||||
|
||||
enum AnycastType : uint8_t
|
||||
|
@ -361,21 +370,39 @@ private:
|
|||
friend class FragmentPriorityList;
|
||||
|
||||
public:
|
||||
Message::Priority GetPriority(void) const { return mPriority; }
|
||||
// Lifetime of an entry in seconds.
|
||||
static constexpr uint8_t kLifetime =
|
||||
#if OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_ENABLE
|
||||
OT_MAX(kReassemblyTimeout, OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_FRAG_TAG_RETAIN_TIME);
|
||||
#else
|
||||
kReassemblyTimeout;
|
||||
#endif
|
||||
|
||||
Message::Priority GetPriority(void) const { return static_cast<Message::Priority>(mPriority); }
|
||||
bool IsExpired(void) const { return (mLifetime == 0); }
|
||||
void DecrementLifetime(void) { mLifetime--; }
|
||||
void ResetLifetime(void) { mLifetime = kReassemblyTimeout; }
|
||||
void ResetLifetime(void) { mLifetime = kLifetime; }
|
||||
|
||||
bool Matches(uint16_t aSrcRloc16, uint16_t aTag) const
|
||||
{
|
||||
return (mSrcRloc16 == aSrcRloc16) && (mDatagramTag == aTag);
|
||||
}
|
||||
|
||||
#if OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_ENABLE
|
||||
bool ShouldDrop(void) const { return mShouldDrop; }
|
||||
void MarkToDrop(void) { mShouldDrop = true; }
|
||||
#endif
|
||||
|
||||
private:
|
||||
uint16_t mSrcRloc16;
|
||||
uint16_t mDatagramTag;
|
||||
Message::Priority mPriority;
|
||||
uint8_t mLifetime;
|
||||
uint16_t mSrcRloc16;
|
||||
uint16_t mDatagramTag;
|
||||
uint8_t mLifetime;
|
||||
uint8_t mPriority : 2;
|
||||
#if OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_ENABLE
|
||||
bool mShouldDrop : 1;
|
||||
#endif
|
||||
|
||||
static_assert(Message::kNumPriorities <= 4, "mPriority as a 2-bit does not fit all `Priority` values");
|
||||
};
|
||||
|
||||
Entry *AllocateEntry(uint16_t aSrcRloc16, uint16_t aTag, Message::Priority aPriority);
|
||||
|
@ -383,7 +410,13 @@ private:
|
|||
bool UpdateOnTimeTick(void);
|
||||
|
||||
private:
|
||||
static constexpr uint16_t kNumEntries = OPENTHREAD_CONFIG_NUM_FRAGMENT_PRIORITY_ENTRIES;
|
||||
static constexpr uint16_t kNumEntries =
|
||||
#if OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_ENABLE
|
||||
OT_MAX(OPENTHREAD_CONFIG_NUM_FRAGMENT_PRIORITY_ENTRIES,
|
||||
OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_FRAG_TAG_ENTRY_LIST_SIZE);
|
||||
#else
|
||||
OPENTHREAD_CONFIG_NUM_FRAGMENT_PRIORITY_ENTRIES;
|
||||
#endif
|
||||
|
||||
Entry mEntries[kNumEntries];
|
||||
};
|
||||
|
@ -433,6 +466,10 @@ private:
|
|||
bool aAddFragHeader = false);
|
||||
void PrepareEmptyFrame(Mac::TxFrame &aFrame, const Mac::Address &aMacDest, bool aAckRequest);
|
||||
|
||||
#if OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_ENABLE
|
||||
Error UpdateEcnOrDrop(Message &aMessage, bool aPreparingToSend = true);
|
||||
Error RemoveAgedMessages(void);
|
||||
#endif
|
||||
void SendMesh(Message &aMessage, Mac::TxFrame &aFrame);
|
||||
void SendDestinationUnreachable(uint16_t aMeshSource, const Message &aMessage);
|
||||
Error UpdateIp6Route(Message &aMessage);
|
||||
|
|
|
@ -53,6 +53,7 @@ Error MeshForwarder::SendMessage(Message &aMessage)
|
|||
|
||||
aMessage.SetOffset(0);
|
||||
aMessage.SetDatagramTag(0);
|
||||
aMessage.SetTimestampToNow();
|
||||
mSendQueue.Enqueue(aMessage);
|
||||
|
||||
switch (aMessage.GetType())
|
||||
|
@ -199,6 +200,11 @@ Error MeshForwarder::EvictMessage(Message::Priority aPriority)
|
|||
Error error = kErrorNotFound;
|
||||
Message *evict = nullptr;
|
||||
|
||||
#if OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_ENABLE
|
||||
error = RemoveAgedMessages();
|
||||
VerifyOrExit(error == kErrorNotFound);
|
||||
#endif
|
||||
|
||||
// Search for a lower priority message to evict
|
||||
for (uint8_t priority = 0; priority < aPriority; priority++)
|
||||
{
|
||||
|
@ -245,8 +251,7 @@ Error MeshForwarder::EvictMessage(Message::Priority aPriority)
|
|||
}
|
||||
|
||||
exit:
|
||||
|
||||
if (error == kErrorNone)
|
||||
if ((error == kErrorNone) && (evict != nullptr))
|
||||
{
|
||||
RemoveMessage(*evict);
|
||||
}
|
||||
|
@ -881,11 +886,18 @@ void MeshForwarder::UpdateFragmentPriority(Lowpan::FragmentHeader &aFragmentHead
|
|||
ExitNow();
|
||||
}
|
||||
|
||||
#if OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_ENABLE
|
||||
OT_UNUSED_VARIABLE(aFragmentLength);
|
||||
#else
|
||||
// We can clear the entry in `mFragmentPriorityList` if it is the
|
||||
// last fragment. But if "delay aware active queue management" is
|
||||
// used we need to keep entry until the message is sent.
|
||||
if (aFragmentHeader.GetDatagramOffset() + aFragmentLength >= aFragmentHeader.GetDatagramSize())
|
||||
{
|
||||
entry->Clear();
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
entry->ResetLifetime();
|
||||
}
|
||||
|
@ -922,6 +934,7 @@ MeshForwarder::FragmentPriorityList::Entry *MeshForwarder::FragmentPriorityList:
|
|||
{
|
||||
if (entry.IsExpired())
|
||||
{
|
||||
entry.Clear();
|
||||
entry.mSrcRloc16 = aSrcRloc16;
|
||||
entry.mDatagramTag = aTag;
|
||||
entry.mPriority = aPriority;
|
||||
|
|
|
@ -42,6 +42,7 @@ Error MeshForwarder::SendMessage(Message &aMessage)
|
|||
aMessage.SetDirectTransmission();
|
||||
aMessage.SetOffset(0);
|
||||
aMessage.SetDatagramTag(0);
|
||||
aMessage.SetTimestampToNow();
|
||||
|
||||
mSendQueue.Enqueue(aMessage);
|
||||
mScheduleTransmissionTask.Post();
|
||||
|
@ -54,6 +55,11 @@ Error MeshForwarder::EvictMessage(Message::Priority aPriority)
|
|||
Error error = kErrorNotFound;
|
||||
Message *message;
|
||||
|
||||
#if OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_ENABLE
|
||||
error = RemoveAgedMessages();
|
||||
VerifyOrExit(error == kErrorNotFound);
|
||||
#endif
|
||||
|
||||
VerifyOrExit((message = mSendQueue.GetTail()) != nullptr);
|
||||
|
||||
if (message->GetPriority() < static_cast<uint8_t>(aPriority))
|
||||
|
|
|
@ -530,6 +530,14 @@
|
|||
*/
|
||||
#define OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE 1
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_ENABLE
|
||||
*
|
||||
* Define to 1 to enable delay-aware queue management for the send queue.
|
||||
*
|
||||
*/
|
||||
#define OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_ENABLE 1
|
||||
|
||||
#if OPENTHREAD_RADIO
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_MAC_SOFTWARE_ACK_TIMEOUT_ENABLE
|
||||
|
|
|
@ -170,6 +170,8 @@ static void Test(TestIphcVector &aVector, bool aCompress, bool aDecompress)
|
|||
if (aCompress)
|
||||
{
|
||||
Lowpan::BufferWriter buffer(result, 127);
|
||||
Message * compressedMsg;
|
||||
Ip6::Ecn ecn;
|
||||
|
||||
VerifyOrQuit((message = sInstance->Get<MessagePool>().Allocate(Message::kTypeIp6)) != nullptr);
|
||||
|
||||
|
@ -192,6 +194,25 @@ static void Test(TestIphcVector &aVector, bool aCompress, bool aDecompress)
|
|||
VerifyOrQuit(compressBytes == aVector.mIphcHeader.mLength, "Lowpan::Compress failed");
|
||||
VerifyOrQuit(message->GetOffset() == aVector.mPayloadOffset, "Lowpan::Compress failed");
|
||||
VerifyOrQuit(memcmp(iphc, result, iphcLength) == 0, "Lowpan::Compress failed");
|
||||
|
||||
// Validate `DecompressEcn()` and `MarkCompressedEcn()`
|
||||
|
||||
VerifyOrQuit((compressedMsg = sInstance->Get<MessagePool>().Allocate(Message::kTypeIp6)) != nullptr);
|
||||
SuccessOrQuit(compressedMsg->AppendBytes(result, compressBytes));
|
||||
|
||||
ecn = sLowpan->DecompressEcn(*compressedMsg, /* aOffset */ 0);
|
||||
VerifyOrQuit(ecn == aVector.GetIpHeader().GetEcn());
|
||||
printf("Decompressed ECN is %d\n", ecn);
|
||||
|
||||
if (ecn != Ip6::kEcnNotCapable)
|
||||
{
|
||||
sLowpan->MarkCompressedEcn(*compressedMsg, /*a aOffset */ 0);
|
||||
ecn = sLowpan->DecompressEcn(*compressedMsg, /* aOffset */ 0);
|
||||
VerifyOrQuit(ecn == Ip6::kEcnMarked);
|
||||
printf("ECN is updated to %d\n", ecn);
|
||||
}
|
||||
|
||||
compressedMsg->Free();
|
||||
}
|
||||
|
||||
message->Free();
|
||||
|
|
|
@ -99,6 +99,14 @@ public:
|
|||
*/
|
||||
void SetMacDestination(uint16_t aAddress) { mMacDestination.SetShort(aAddress); }
|
||||
|
||||
/**
|
||||
* This method gets the IPv6 header
|
||||
*
|
||||
* @returns the IPv6 header.
|
||||
*
|
||||
*/
|
||||
const Ip6::Header &GetIpHeader(void) const { return mIpHeader; }
|
||||
|
||||
/**
|
||||
* This method initializes IPv6 Header.
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue