498 lines
17 KiB
C++
498 lines
17 KiB
C++
/*
|
|
* Copyright (c) 2012 Andrew McGregor
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-only
|
|
*
|
|
* Codel, the COntrolled DELay Queueing discipline
|
|
* Based on ns2 simulation code presented by Kathie Nichols
|
|
*
|
|
* This port based on linux kernel code by
|
|
* Authors:
|
|
* Dave Täht <d@taht.net>
|
|
* Eric Dumazet <edumazet@google.com>
|
|
*
|
|
* Ported to ns-3 by: Andrew McGregor <andrewmcgr@gmail.com>
|
|
*/
|
|
|
|
#include "codel-queue-disc.h"
|
|
|
|
#include "ns3/abort.h"
|
|
#include "ns3/drop-tail-queue.h"
|
|
#include "ns3/enum.h"
|
|
#include "ns3/log.h"
|
|
#include "ns3/object-factory.h"
|
|
#include "ns3/uinteger.h"
|
|
|
|
namespace ns3
|
|
{
|
|
|
|
NS_LOG_COMPONENT_DEFINE("CoDelQueueDisc");
|
|
|
|
/**
|
|
* Performs a reciprocal divide, similar to the
|
|
* Linux kernel reciprocal_divide function
|
|
* \param A numerator
|
|
* \param R reciprocal of the denominator B
|
|
* \return the value of A/B
|
|
*/
|
|
/* borrowed from the linux kernel */
|
|
static inline uint32_t
|
|
ReciprocalDivide(uint32_t A, uint32_t R)
|
|
{
|
|
return (uint32_t)(((uint64_t)A * R) >> 32);
|
|
}
|
|
|
|
/* end kernel borrowings */
|
|
|
|
/**
|
|
* Returns the current time translated in CoDel time representation
|
|
* \return the current time
|
|
*/
|
|
static uint32_t
|
|
CoDelGetTime()
|
|
{
|
|
Time time = Simulator::Now();
|
|
uint64_t ns = time.GetNanoSeconds();
|
|
|
|
return static_cast<uint32_t>(ns >> CODEL_SHIFT);
|
|
}
|
|
|
|
NS_OBJECT_ENSURE_REGISTERED(CoDelQueueDisc);
|
|
|
|
TypeId
|
|
CoDelQueueDisc::GetTypeId()
|
|
{
|
|
static TypeId tid =
|
|
TypeId("ns3::CoDelQueueDisc")
|
|
.SetParent<QueueDisc>()
|
|
.SetGroupName("TrafficControl")
|
|
.AddConstructor<CoDelQueueDisc>()
|
|
.AddAttribute("UseEcn",
|
|
"True to use ECN (packets are marked instead of being dropped)",
|
|
BooleanValue(false),
|
|
MakeBooleanAccessor(&CoDelQueueDisc::m_useEcn),
|
|
MakeBooleanChecker())
|
|
.AddAttribute("UseL4s",
|
|
"True to use L4S (only ECT1 packets are marked at CE threshold)",
|
|
BooleanValue(false),
|
|
MakeBooleanAccessor(&CoDelQueueDisc::m_useL4s),
|
|
MakeBooleanChecker())
|
|
.AddAttribute(
|
|
"MaxSize",
|
|
"The maximum number of packets/bytes accepted by this queue disc.",
|
|
QueueSizeValue(QueueSize(QueueSizeUnit::BYTES, 1500 * DEFAULT_CODEL_LIMIT)),
|
|
MakeQueueSizeAccessor(&QueueDisc::SetMaxSize, &QueueDisc::GetMaxSize),
|
|
MakeQueueSizeChecker())
|
|
.AddAttribute("MinBytes",
|
|
"The CoDel algorithm minbytes parameter.",
|
|
UintegerValue(1500),
|
|
MakeUintegerAccessor(&CoDelQueueDisc::m_minBytes),
|
|
MakeUintegerChecker<uint32_t>())
|
|
.AddAttribute("Interval",
|
|
"The CoDel algorithm interval",
|
|
StringValue("100ms"),
|
|
MakeTimeAccessor(&CoDelQueueDisc::m_interval),
|
|
MakeTimeChecker())
|
|
.AddAttribute("Target",
|
|
"The CoDel algorithm target queue delay",
|
|
StringValue("5ms"),
|
|
MakeTimeAccessor(&CoDelQueueDisc::m_target),
|
|
MakeTimeChecker())
|
|
.AddAttribute("CeThreshold",
|
|
"The CoDel CE threshold for marking packets",
|
|
TimeValue(Time::Max()),
|
|
MakeTimeAccessor(&CoDelQueueDisc::m_ceThreshold),
|
|
MakeTimeChecker())
|
|
.AddTraceSource("Count",
|
|
"CoDel count",
|
|
MakeTraceSourceAccessor(&CoDelQueueDisc::m_count),
|
|
"ns3::TracedValueCallback::Uint32")
|
|
.AddTraceSource("LastCount",
|
|
"CoDel lastcount",
|
|
MakeTraceSourceAccessor(&CoDelQueueDisc::m_lastCount),
|
|
"ns3::TracedValueCallback::Uint32")
|
|
.AddTraceSource("DropState",
|
|
"Dropping state",
|
|
MakeTraceSourceAccessor(&CoDelQueueDisc::m_dropping),
|
|
"ns3::TracedValueCallback::Bool")
|
|
.AddTraceSource("DropNext",
|
|
"Time until next packet drop",
|
|
MakeTraceSourceAccessor(&CoDelQueueDisc::m_dropNext),
|
|
"ns3::TracedValueCallback::Uint32");
|
|
|
|
return tid;
|
|
}
|
|
|
|
CoDelQueueDisc::CoDelQueueDisc()
|
|
: QueueDisc(QueueDiscSizePolicy::SINGLE_INTERNAL_QUEUE),
|
|
m_count(0),
|
|
m_lastCount(0),
|
|
m_dropping(false),
|
|
m_recInvSqrt(~0U >> REC_INV_SQRT_SHIFT),
|
|
m_firstAboveTime(0),
|
|
m_dropNext(0)
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
}
|
|
|
|
CoDelQueueDisc::~CoDelQueueDisc()
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
}
|
|
|
|
uint16_t
|
|
CoDelQueueDisc::NewtonStep(uint16_t recInvSqrt, uint32_t count)
|
|
{
|
|
NS_LOG_FUNCTION_NOARGS();
|
|
uint32_t invsqrt = ((uint32_t)recInvSqrt) << REC_INV_SQRT_SHIFT;
|
|
uint32_t invsqrt2 = ((uint64_t)invsqrt * invsqrt) >> 32;
|
|
uint64_t val = (3LL << 32) - ((uint64_t)count * invsqrt2);
|
|
|
|
val >>= 2; /* avoid overflow */
|
|
val = (val * invsqrt) >> (32 - 2 + 1);
|
|
return static_cast<uint16_t>(val >> REC_INV_SQRT_SHIFT);
|
|
}
|
|
|
|
uint32_t
|
|
CoDelQueueDisc::ControlLaw(uint32_t t, uint32_t interval, uint32_t recInvSqrt)
|
|
{
|
|
NS_LOG_FUNCTION_NOARGS();
|
|
return t + ReciprocalDivide(interval, recInvSqrt << REC_INV_SQRT_SHIFT);
|
|
}
|
|
|
|
bool
|
|
CoDelQueueDisc::DoEnqueue(Ptr<QueueDiscItem> item)
|
|
{
|
|
NS_LOG_FUNCTION(this << item);
|
|
|
|
if (GetCurrentSize() + item > GetMaxSize())
|
|
{
|
|
NS_LOG_LOGIC("Queue full -- dropping pkt");
|
|
DropBeforeEnqueue(item, OVERLIMIT_DROP);
|
|
return false;
|
|
}
|
|
|
|
bool retval = GetInternalQueue(0)->Enqueue(item);
|
|
|
|
// If Queue::Enqueue fails, QueueDisc::DropBeforeEnqueue is called by the
|
|
// internal queue because QueueDisc::AddInternalQueue sets the trace callback
|
|
|
|
NS_LOG_LOGIC("Number packets " << GetInternalQueue(0)->GetNPackets());
|
|
NS_LOG_LOGIC("Number bytes " << GetInternalQueue(0)->GetNBytes());
|
|
|
|
return retval;
|
|
}
|
|
|
|
bool
|
|
CoDelQueueDisc::OkToDrop(Ptr<QueueDiscItem> item, uint32_t now)
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
bool okToDrop;
|
|
|
|
if (!item)
|
|
{
|
|
m_firstAboveTime = 0;
|
|
return false;
|
|
}
|
|
|
|
Time delta = Simulator::Now() - item->GetTimeStamp();
|
|
NS_LOG_INFO("Sojourn time " << delta.As(Time::MS));
|
|
uint32_t sojournTime = Time2CoDel(delta);
|
|
|
|
if (CoDelTimeBefore(sojournTime, Time2CoDel(m_target)) ||
|
|
GetInternalQueue(0)->GetNBytes() < m_minBytes)
|
|
{
|
|
// went below so we'll stay below for at least q->interval
|
|
NS_LOG_LOGIC("Sojourn time is below target or number of bytes in queue is less than "
|
|
"minBytes; packet should not be dropped");
|
|
m_firstAboveTime = 0;
|
|
return false;
|
|
}
|
|
okToDrop = false;
|
|
if (m_firstAboveTime == 0)
|
|
{
|
|
/* just went above from below. If we stay above
|
|
* for at least q->interval we'll say it's ok to drop
|
|
*/
|
|
NS_LOG_LOGIC("Sojourn time has just gone above target from below, need to stay above for "
|
|
"at least q->interval before packet can be dropped. ");
|
|
m_firstAboveTime = now + Time2CoDel(m_interval);
|
|
}
|
|
else if (CoDelTimeAfter(now, m_firstAboveTime))
|
|
{
|
|
NS_LOG_LOGIC("Sojourn time has been above target for at least q->interval; it's OK to "
|
|
"(possibly) drop packet.");
|
|
okToDrop = true;
|
|
}
|
|
return okToDrop;
|
|
}
|
|
|
|
Ptr<QueueDiscItem>
|
|
CoDelQueueDisc::DoDequeue()
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
|
|
Ptr<QueueDiscItem> item = GetInternalQueue(0)->Dequeue();
|
|
if (!item)
|
|
{
|
|
// Leave dropping state when queue is empty
|
|
m_dropping = false;
|
|
NS_LOG_LOGIC("Queue empty");
|
|
return nullptr;
|
|
}
|
|
uint32_t ldelay = Time2CoDel(Simulator::Now() - item->GetTimeStamp());
|
|
if (item && m_useL4s)
|
|
{
|
|
uint8_t tosByte = 0;
|
|
if (item->GetUint8Value(QueueItem::IP_DSFIELD, tosByte) &&
|
|
(((tosByte & 0x3) == 1) || (tosByte & 0x3) == 3))
|
|
{
|
|
if ((tosByte & 0x3) == 1)
|
|
{
|
|
NS_LOG_DEBUG("ECT1 packet " << static_cast<uint16_t>(tosByte & 0x3));
|
|
}
|
|
else
|
|
{
|
|
NS_LOG_DEBUG("CE packet " << static_cast<uint16_t>(tosByte & 0x3));
|
|
}
|
|
|
|
if (CoDelTimeAfter(ldelay, Time2CoDel(m_ceThreshold)) &&
|
|
Mark(item, CE_THRESHOLD_EXCEEDED_MARK))
|
|
{
|
|
NS_LOG_LOGIC("Marking due to CeThreshold " << m_ceThreshold.GetSeconds());
|
|
}
|
|
return item;
|
|
}
|
|
}
|
|
|
|
uint32_t now = CoDelGetTime();
|
|
|
|
NS_LOG_LOGIC("Popped " << item);
|
|
NS_LOG_LOGIC("Number packets remaining " << GetInternalQueue(0)->GetNPackets());
|
|
NS_LOG_LOGIC("Number bytes remaining " << GetInternalQueue(0)->GetNBytes());
|
|
|
|
// Determine if item should be dropped
|
|
bool okToDrop = OkToDrop(item, now);
|
|
bool isMarked = false;
|
|
|
|
if (m_dropping)
|
|
{ // In the dropping state (sojourn time has gone above target and hasn't come down yet)
|
|
// Check if we can leave the dropping state or next drop should occur
|
|
NS_LOG_LOGIC("In dropping state, check if it's OK to leave or next drop should occur");
|
|
if (!okToDrop)
|
|
{
|
|
/* sojourn time fell below target - leave dropping state */
|
|
NS_LOG_LOGIC("Sojourn time goes below target, it's OK to leave dropping state.");
|
|
m_dropping = false;
|
|
}
|
|
else if (CoDelTimeAfterEq(now, m_dropNext))
|
|
{
|
|
while (m_dropping && CoDelTimeAfterEq(now, m_dropNext))
|
|
{
|
|
++m_count;
|
|
m_recInvSqrt = NewtonStep(m_recInvSqrt, m_count);
|
|
// It's time for the next drop. Drop the current packet and
|
|
// dequeue the next. The dequeue might take us out of dropping
|
|
// state. If not, schedule the next drop.
|
|
// A large amount of packets in queue might result in drop
|
|
// rates so high that the next drop should happen now,
|
|
// hence the while loop.
|
|
if (m_useEcn && Mark(item, TARGET_EXCEEDED_MARK))
|
|
{
|
|
isMarked = true;
|
|
NS_LOG_LOGIC("Sojourn time is still above target and it's time for next drop "
|
|
"or mark; marking "
|
|
<< item);
|
|
NS_LOG_LOGIC("Running ControlLaw for input m_dropNext: " << (double)m_dropNext /
|
|
1000000);
|
|
m_dropNext = ControlLaw(now, Time2CoDel(m_interval), m_recInvSqrt);
|
|
NS_LOG_LOGIC("Scheduled next drop at " << (double)m_dropNext / 1000000);
|
|
goto end;
|
|
}
|
|
NS_LOG_LOGIC(
|
|
"Sojourn time is still above target and it's time for next drop; dropping "
|
|
<< item);
|
|
DropAfterDequeue(item, TARGET_EXCEEDED_DROP);
|
|
|
|
item = GetInternalQueue(0)->Dequeue();
|
|
|
|
if (item)
|
|
{
|
|
NS_LOG_LOGIC("Popped " << item);
|
|
NS_LOG_LOGIC("Number packets remaining " << GetInternalQueue(0)->GetNPackets());
|
|
NS_LOG_LOGIC("Number bytes remaining " << GetInternalQueue(0)->GetNBytes());
|
|
}
|
|
|
|
if (!OkToDrop(item, now))
|
|
{
|
|
/* leave dropping state */
|
|
NS_LOG_LOGIC("Leaving dropping state");
|
|
m_dropping = false;
|
|
}
|
|
else
|
|
{
|
|
/* schedule the next drop */
|
|
NS_LOG_LOGIC("Running ControlLaw for input m_dropNext: " << (double)m_dropNext /
|
|
1000000);
|
|
m_dropNext = ControlLaw(m_dropNext, Time2CoDel(m_interval), m_recInvSqrt);
|
|
NS_LOG_LOGIC("Scheduled next drop at " << (double)m_dropNext / 1000000);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Not in the dropping state
|
|
// Decide if we have to enter the dropping state and drop the first packet
|
|
NS_LOG_LOGIC("Not in dropping state; decide if we have to enter the state and drop the "
|
|
"first packet");
|
|
if (okToDrop)
|
|
{
|
|
if (m_useEcn && Mark(item, TARGET_EXCEEDED_MARK))
|
|
{
|
|
isMarked = true;
|
|
NS_LOG_LOGIC("Sojourn time goes above target, marking the first packet "
|
|
<< item << " and entering the dropping state");
|
|
}
|
|
else
|
|
{
|
|
// Drop the first packet and enter dropping state unless the queue is empty
|
|
NS_LOG_LOGIC("Sojourn time goes above target, dropping the first packet "
|
|
<< item << " and entering the dropping state");
|
|
DropAfterDequeue(item, TARGET_EXCEEDED_DROP);
|
|
item = GetInternalQueue(0)->Dequeue();
|
|
if (item)
|
|
{
|
|
NS_LOG_LOGIC("Popped " << item);
|
|
NS_LOG_LOGIC("Number packets remaining " << GetInternalQueue(0)->GetNPackets());
|
|
NS_LOG_LOGIC("Number bytes remaining " << GetInternalQueue(0)->GetNBytes());
|
|
}
|
|
OkToDrop(item, now);
|
|
}
|
|
m_dropping = true;
|
|
/*
|
|
* if min went above target close to when we last went below it
|
|
* assume that the drop rate that controlled the queue on the
|
|
* last cycle is a good starting point to control it now.
|
|
*/
|
|
int delta = m_count - m_lastCount;
|
|
if (delta > 1 && CoDelTimeBefore(now - m_dropNext, 16 * Time2CoDel(m_interval)))
|
|
{
|
|
m_count = delta;
|
|
m_recInvSqrt = NewtonStep(m_recInvSqrt, m_count);
|
|
}
|
|
else
|
|
{
|
|
m_count = 1;
|
|
m_recInvSqrt = ~0U >> REC_INV_SQRT_SHIFT;
|
|
}
|
|
m_lastCount = m_count;
|
|
NS_LOG_LOGIC("Running ControlLaw for input now: " << (double)now);
|
|
m_dropNext = ControlLaw(now, Time2CoDel(m_interval), m_recInvSqrt);
|
|
NS_LOG_LOGIC("Scheduled next drop at " << (double)m_dropNext / 1000000 << " now "
|
|
<< (double)now / 1000000);
|
|
}
|
|
}
|
|
end:
|
|
ldelay = Time2CoDel(Simulator::Now() - item->GetTimeStamp());
|
|
// In Linux, this branch of code is executed even if the packet has been marked
|
|
// according to the target delay above. If the ns-3 code were to do the same here,
|
|
// it would result in two counts of mark in the queue statistics. Therefore, we
|
|
// use the isMarked flag to suppress a second attempt at marking.
|
|
if (!isMarked && item && !m_useL4s && m_useEcn &&
|
|
CoDelTimeAfter(ldelay, Time2CoDel(m_ceThreshold)) && Mark(item, CE_THRESHOLD_EXCEEDED_MARK))
|
|
{
|
|
NS_LOG_LOGIC("Marking due to CeThreshold " << m_ceThreshold.GetSeconds());
|
|
}
|
|
return item;
|
|
}
|
|
|
|
Time
|
|
CoDelQueueDisc::GetTarget()
|
|
{
|
|
return m_target;
|
|
}
|
|
|
|
Time
|
|
CoDelQueueDisc::GetInterval()
|
|
{
|
|
return m_interval;
|
|
}
|
|
|
|
uint32_t
|
|
CoDelQueueDisc::GetDropNext()
|
|
{
|
|
return m_dropNext;
|
|
}
|
|
|
|
bool
|
|
CoDelQueueDisc::CoDelTimeAfter(uint32_t a, uint32_t b)
|
|
{
|
|
return ((int64_t)(a) - (int64_t)(b) > 0);
|
|
}
|
|
|
|
bool
|
|
CoDelQueueDisc::CoDelTimeAfterEq(uint32_t a, uint32_t b)
|
|
{
|
|
return ((int64_t)(a) - (int64_t)(b) >= 0);
|
|
}
|
|
|
|
bool
|
|
CoDelQueueDisc::CoDelTimeBefore(uint32_t a, uint32_t b)
|
|
{
|
|
return ((int64_t)(a) - (int64_t)(b) < 0);
|
|
}
|
|
|
|
bool
|
|
CoDelQueueDisc::CoDelTimeBeforeEq(uint32_t a, uint32_t b)
|
|
{
|
|
return ((int64_t)(a) - (int64_t)(b) <= 0);
|
|
}
|
|
|
|
uint32_t
|
|
CoDelQueueDisc::Time2CoDel(Time t)
|
|
{
|
|
return static_cast<uint32_t>(t.GetNanoSeconds() >> CODEL_SHIFT);
|
|
}
|
|
|
|
bool
|
|
CoDelQueueDisc::CheckConfig()
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
if (GetNQueueDiscClasses() > 0)
|
|
{
|
|
NS_LOG_ERROR("CoDelQueueDisc cannot have classes");
|
|
return false;
|
|
}
|
|
|
|
if (GetNPacketFilters() > 0)
|
|
{
|
|
NS_LOG_ERROR("CoDelQueueDisc cannot have packet filters");
|
|
return false;
|
|
}
|
|
|
|
if (GetNInternalQueues() == 0)
|
|
{
|
|
// add a DropTail queue
|
|
AddInternalQueue(
|
|
CreateObjectWithAttributes<DropTailQueue<QueueDiscItem>>("MaxSize",
|
|
QueueSizeValue(GetMaxSize())));
|
|
}
|
|
|
|
if (GetNInternalQueues() != 1)
|
|
{
|
|
NS_LOG_ERROR("CoDelQueueDisc needs 1 internal queue");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
CoDelQueueDisc::InitializeParams()
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
}
|
|
|
|
} // namespace ns3
|