diff --git a/doc/models/Makefile b/doc/models/Makefile index ac3b2f385..39c97018c 100644 --- a/doc/models/Makefile +++ b/doc/models/Makefile @@ -80,6 +80,7 @@ SOURCES = \ $(SRC)/traffic-control/doc/pfifo-fast.rst \ $(SRC)/traffic-control/doc/red.rst \ $(SRC)/traffic-control/doc/codel.rst \ + $(SRC)/traffic-control/doc/fq-codel.rst \ $(SRC)/spectrum/doc/spectrum.rst \ $(SRC)/stats/doc/adaptor.rst \ $(SRC)/stats/doc/aggregator.rst \ diff --git a/doc/models/source/traffic-control.rst b/doc/models/source/traffic-control.rst index 0f5533628..31645c881 100644 --- a/doc/models/source/traffic-control.rst +++ b/doc/models/source/traffic-control.rst @@ -8,3 +8,4 @@ Traffic Control Layer pfifo-fast red codel + fq-codel diff --git a/src/internet/model/ipv4-packet-filter.cc b/src/internet/model/ipv4-packet-filter.cc index d7db41f7d..cca967f18 100644 --- a/src/internet/model/ipv4-packet-filter.cc +++ b/src/internet/model/ipv4-packet-filter.cc @@ -18,10 +18,13 @@ * * Authors: Stefano Avallone * Tom Henderson + * Pasquale Imputato */ #include "ns3/log.h" #include "ns3/enum.h" +#include "ns3/tcp-header.h" +#include "ns3/udp-header.h" #include "ipv4-queue-disc-item.h" #include "ipv4-packet-filter.h" @@ -58,4 +61,90 @@ Ipv4PacketFilter::CheckProtocol (Ptr item) const return (DynamicCast (item) != 0); } +// ------------------------------------------------------------------------- // + +NS_OBJECT_ENSURE_REGISTERED (FqCoDelIpv4PacketFilter); + +TypeId +FqCoDelIpv4PacketFilter::GetTypeId (void) +{ + static TypeId tid = TypeId ("ns3::FqCoDelIpv4PacketFilter") + .SetParent () + .SetGroupName ("Internet") + .AddConstructor () + .AddAttribute ("Perturbation", + "The salt used as an additional input to the hash function of this filter", + UintegerValue (0), + MakeUintegerAccessor (&FqCoDelIpv4PacketFilter::m_perturbation), + MakeUintegerChecker ()) + ; + return tid; +} + +FqCoDelIpv4PacketFilter::FqCoDelIpv4PacketFilter () +{ + NS_LOG_FUNCTION (this); +} + +FqCoDelIpv4PacketFilter::~FqCoDelIpv4PacketFilter () +{ + NS_LOG_FUNCTION (this); +} + +int32_t +FqCoDelIpv4PacketFilter::DoClassify (Ptr item) const +{ + NS_LOG_FUNCTION (this << item); + Ptr ipv4Item = DynamicCast (item); + + NS_ASSERT (ipv4Item != 0); + + Ipv4Header hdr = ipv4Item->GetHeader (); + Ipv4Address src = hdr.GetSource (); + Ipv4Address dest = hdr.GetDestination (); + uint8_t prot = hdr.GetProtocol (); + uint16_t fragOffset = hdr.GetFragmentOffset (); + + TcpHeader tcpHdr; + UdpHeader udpHdr; + uint16_t srcPort = 0; + uint16_t destPort = 0; + + Ptr pkt = ipv4Item->GetPacket (); + + if (prot == 6 && fragOffset == 0) // TCP + { + pkt->PeekHeader (tcpHdr); + srcPort = tcpHdr.GetSourcePort (); + destPort = tcpHdr.GetDestinationPort (); + } + else if (prot == 17 && fragOffset == 0) // UDP + { + pkt->PeekHeader (udpHdr); + srcPort = udpHdr.GetSourcePort (); + destPort = udpHdr.GetDestinationPort (); + } + + /* serialize the 5-tuple and the perturbation in buf */ + uint8_t buf[17]; + src.Serialize (buf); + dest.Serialize (buf + 4); + buf[8] = prot; + buf[9] = (srcPort >> 8) & 0xff; + buf[10] = srcPort & 0xff; + buf[11] = (destPort >> 8) & 0xff; + buf[12] = destPort & 0xff; + buf[13] = (m_perturbation >> 24) & 0xff; + buf[14] = (m_perturbation >> 16) & 0xff; + buf[15] = (m_perturbation >> 8) & 0xff; + buf[16] = m_perturbation & 0xff; + + /* Linux calculates the jhash2 (jenkins hash), we calculate the murmur3 */ + uint32_t hash = Hash32 ((char*) buf, 17); + + NS_LOG_DEBUG ("Found Ipv4 packet; hash value " << hash); + + return hash; +} + } // namespace ns3 diff --git a/src/internet/model/ipv4-packet-filter.h b/src/internet/model/ipv4-packet-filter.h index 3c7eec919..3e9af1a57 100644 --- a/src/internet/model/ipv4-packet-filter.h +++ b/src/internet/model/ipv4-packet-filter.h @@ -18,6 +18,7 @@ * * Authors: Stefano Avallone * Tom Henderson + * Pasquale Imputato */ #ifndef IPV4_PACKET_FILTER_H @@ -50,6 +51,31 @@ private: virtual int32_t DoClassify (Ptr item) const = 0; }; + +/** + * \ingroup internet + * + * FqCoDelIpv4PacketFilter is the filter to be added to the FQCoDel + * queue disc to simulate the behavior of the fq-codel Linux queue disc. + * + */ +class FqCoDelIpv4PacketFilter : public Ipv4PacketFilter { +public: + /** + * \brief Get the type ID. + * \return the object TypeId + */ + static TypeId GetTypeId (void); + + FqCoDelIpv4PacketFilter (); + virtual ~FqCoDelIpv4PacketFilter (); + +private: + virtual int32_t DoClassify (Ptr item) const; + + uint32_t m_perturbation; //!< hash perturbation value +}; + } // namespace ns3 #endif /* IPV4_PACKET_FILTER */ diff --git a/src/internet/model/ipv6-packet-filter.cc b/src/internet/model/ipv6-packet-filter.cc index c7c54976f..abec7999c 100644 --- a/src/internet/model/ipv6-packet-filter.cc +++ b/src/internet/model/ipv6-packet-filter.cc @@ -18,10 +18,13 @@ * * Authors: Stefano Avallone * Tom Henderson + * Pasquale Imputato */ #include "ns3/log.h" #include "ns3/enum.h" +#include "ns3/tcp-header.h" +#include "ns3/udp-header.h" #include "ipv6-queue-disc-item.h" #include "ipv6-packet-filter.h" @@ -58,4 +61,89 @@ Ipv6PacketFilter::CheckProtocol (Ptr item) const return (DynamicCast (item) != 0); } +// ------------------------------------------------------------------------- // + +NS_OBJECT_ENSURE_REGISTERED (FqCoDelIpv6PacketFilter); + +TypeId +FqCoDelIpv6PacketFilter::GetTypeId (void) +{ + static TypeId tid = TypeId ("ns3::FqCoDelIpv6PacketFilter") + .SetParent () + .SetGroupName ("Internet") + .AddConstructor () + .AddAttribute ("Perturbation", + "The salt used as an additional input to the hash function of this filter", + UintegerValue (0), + MakeUintegerAccessor (&FqCoDelIpv6PacketFilter::m_perturbation), + MakeUintegerChecker ()) + ; + return tid; +} + +FqCoDelIpv6PacketFilter::FqCoDelIpv6PacketFilter () +{ + NS_LOG_FUNCTION (this); +} + +FqCoDelIpv6PacketFilter::~FqCoDelIpv6PacketFilter () +{ + NS_LOG_FUNCTION (this); +} + +int32_t +FqCoDelIpv6PacketFilter::DoClassify (Ptr< QueueDiscItem > item) const +{ + NS_LOG_FUNCTION (this << item); + Ptr ipv6Item = DynamicCast (item); + + NS_ASSERT (ipv6Item != 0); + + Ipv6Header hdr = ipv6Item->GetHeader (); + Ipv6Address src = hdr.GetSourceAddress (); + Ipv6Address dest = hdr.GetDestinationAddress (); + uint8_t prot = hdr.GetNextHeader (); + + TcpHeader tcpHdr; + UdpHeader udpHdr; + uint16_t srcPort = 0; + uint16_t destPort = 0; + + Ptr pkt = ipv6Item->GetPacket (); + + if (prot == 6) // TCP + { + pkt->PeekHeader (tcpHdr); + srcPort = tcpHdr.GetSourcePort (); + destPort = tcpHdr.GetDestinationPort (); + } + else if (prot == 17) // UDP + { + pkt->PeekHeader (udpHdr); + srcPort = udpHdr.GetSourcePort (); + destPort = udpHdr.GetDestinationPort (); + } + + /* serialize the 5-tuple and the perturbation in buf */ + uint8_t buf[41]; + src.Serialize (buf); + dest.Serialize (buf + 16); + buf[32] = prot; + buf[33] = (srcPort >> 8) & 0xff; + buf[34] = srcPort & 0xff; + buf[35] = (destPort >> 8) & 0xff; + buf[36] = destPort & 0xff; + buf[37] = (m_perturbation >> 24) & 0xff; + buf[38] = (m_perturbation >> 16) & 0xff; + buf[39] = (m_perturbation >> 8) & 0xff; + buf[40] = m_perturbation & 0xff; + + /* Linux calculates the jhash2 (jenkins hash), we calculate the murmur3 */ + uint32_t hash = Hash32 ((char*) buf, 41); + + NS_LOG_DEBUG ("Found Ipv6 packet; hash of the five tuple " << hash); + + return hash; +} + } // namespace ns3 diff --git a/src/internet/model/ipv6-packet-filter.h b/src/internet/model/ipv6-packet-filter.h index d79c974e2..97b72e04e 100644 --- a/src/internet/model/ipv6-packet-filter.h +++ b/src/internet/model/ipv6-packet-filter.h @@ -18,6 +18,7 @@ * * Authors: Stefano Avallone * Tom Henderson + * Pasquale Imputato */ #ifndef IPV6_PACKET_FILTER_H @@ -50,6 +51,31 @@ private: virtual int32_t DoClassify (Ptr item) const = 0; }; + +/** + * \ingroup internet + * + * FqCoDelIpv6PacketFilter is the filter to be added to the FQCoDel + * queue disc to simulate the behavior of the fq-codel Linux queue disc. + * + */ +class FqCoDelIpv6PacketFilter : public Ipv6PacketFilter { +public: + /** + * \brief Get the type ID. + * \return the object TypeId + */ + static TypeId GetTypeId (void); + + FqCoDelIpv6PacketFilter (); + virtual ~FqCoDelIpv6PacketFilter (); + +private: + virtual int32_t DoClassify (Ptr item) const; + + uint32_t m_perturbation; //!< hash perturbation value +}; + } // namespace ns3 #endif /* IPV6_PACKET_FILTER */ diff --git a/src/test/ns3tc/fq-codel-queue-disc-test-suite.cc b/src/test/ns3tc/fq-codel-queue-disc-test-suite.cc new file mode 100644 index 000000000..0fe729288 --- /dev/null +++ b/src/test/ns3tc/fq-codel-queue-disc-test-suite.cc @@ -0,0 +1,493 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2016 Universita' degli Studi di Napoli Federico II + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Authors: Pasquale Imputato + * Stefano Avallone +*/ + +#include "ns3/test.h" +#include "ns3/fq-codel-queue-disc.h" +#include "ns3/ipv4-header.h" +#include "ns3/ipv4-packet-filter.h" +#include "ns3/ipv4-queue-disc-item.h" +#include "ns3/ipv4-address.h" +#include "ns3/ipv6-header.h" +#include "ns3/ipv6-packet-filter.h" +#include "ns3/ipv6-queue-disc-item.h" +#include "ns3/tcp-header.h" +#include "ns3/udp-header.h" +#include "ns3/uinteger.h" +#include "ns3/pointer.h" + +using namespace ns3; + +/** + * This class tests packets for which there is no suitable filter + */ +class FqCoDelQueueDiscNoSuitableFilter : public TestCase +{ +public: + FqCoDelQueueDiscNoSuitableFilter (); + virtual ~FqCoDelQueueDiscNoSuitableFilter (); + +private: + virtual void DoRun (void); +}; + +FqCoDelQueueDiscNoSuitableFilter::FqCoDelQueueDiscNoSuitableFilter () + : TestCase ("Test packets that are not classified by any filter") +{ +} + +FqCoDelQueueDiscNoSuitableFilter::~FqCoDelQueueDiscNoSuitableFilter () +{ +} + +void +FqCoDelQueueDiscNoSuitableFilter::DoRun (void) +{ + // Packets that cannot be classified by the available filters should be dropped + Ptr queueDisc = CreateObjectWithAttributes ("Packet limit", UintegerValue (4)); + Ptr filter = CreateObject (); + queueDisc->AddPacketFilter (filter); + + queueDisc->SetQuantum (1500); + queueDisc->Initialize (); + + Ptr p; + p = Create (); + Ptr item; + Ipv6Header ipv6Header; + Address dest; + item = Create (p, dest, 0, ipv6Header); + queueDisc->Enqueue (item); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetNQueueDiscClasses (), 0, "no flow queue should have been created"); + + p = Create (reinterpret_cast ("hello, world"), 12); + item = Create (p, dest, 0, ipv6Header); + queueDisc->Enqueue (item); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetNQueueDiscClasses (), 0, "no flow queue should have been created"); +} + +/** + * This class tests the IP flows separation and the packet limit + */ +class FqCoDelQueueDiscIPFlowsSeparationAndPacketLimit : public TestCase +{ +public: + FqCoDelQueueDiscIPFlowsSeparationAndPacketLimit (); + virtual ~FqCoDelQueueDiscIPFlowsSeparationAndPacketLimit (); + +private: + virtual void DoRun (void); + void AddPacket (Ptr queue, Ipv4Header hdr); +}; + +FqCoDelQueueDiscIPFlowsSeparationAndPacketLimit::FqCoDelQueueDiscIPFlowsSeparationAndPacketLimit () + : TestCase ("Test IP flows separation and packet limit") +{ +} + +FqCoDelQueueDiscIPFlowsSeparationAndPacketLimit::~FqCoDelQueueDiscIPFlowsSeparationAndPacketLimit () +{ +} + +void +FqCoDelQueueDiscIPFlowsSeparationAndPacketLimit::AddPacket (Ptr queue, Ipv4Header hdr) +{ + Ptr p = Create (100); + Address dest; + Ptr item = Create (p, dest, 0, hdr); + queue->Enqueue (item); +} + +void +FqCoDelQueueDiscIPFlowsSeparationAndPacketLimit::DoRun (void) +{ + Ptr queueDisc = CreateObjectWithAttributes ("Packet limit", UintegerValue (4)); + Ptr ipv6Filter = CreateObject (); + Ptr ipv4Filter = CreateObject (); + queueDisc->AddPacketFilter (ipv6Filter); + queueDisc->AddPacketFilter (ipv4Filter); + + queueDisc->SetQuantum (1500); + queueDisc->Initialize (); + + Ipv4Header hdr; + hdr.SetPayloadSize (100); + hdr.SetSource (Ipv4Address ("10.10.1.1")); + hdr.SetDestination (Ipv4Address ("10.10.1.2")); + hdr.SetProtocol (7); + + // Add three packets from the first flow + AddPacket (queueDisc, hdr); + AddPacket (queueDisc, hdr); + AddPacket (queueDisc, hdr); + NS_TEST_ASSERT_MSG_EQ (queueDisc->QueueDisc::GetNPackets (), 3, "unexpected number of packets in the queue disc"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (0)->GetQueueDisc ()->GetNPackets (), 3, "unexpected number of packets in the flow queue"); + + // Add two packets from the second flow + hdr.SetDestination (Ipv4Address ("10.10.1.7")); + // Add the first packet + AddPacket (queueDisc, hdr); + NS_TEST_ASSERT_MSG_EQ (queueDisc->QueueDisc::GetNPackets (), 4, "unexpected number of packets in the queue disc"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (0)->GetQueueDisc ()->GetNPackets (), 3, "unexpected number of packets in the flow queue"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (1)->GetQueueDisc ()->GetNPackets (), 1, "unexpected number of packets in the flow queue"); + // Add the second packet that causes two packets to be dropped from the fat flow (max backlog = 300, threshold = 150) + AddPacket (queueDisc, hdr); + NS_TEST_ASSERT_MSG_EQ (queueDisc->QueueDisc::GetNPackets (), 3, "unexpected number of packets in the queue disc"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (0)->GetQueueDisc ()->GetNPackets (), 1, "unexpected number of packets in the flow queue"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (1)->GetQueueDisc ()->GetNPackets (), 2, "unexpected number of packets in the flow queue"); +} + +/** + * This class tests the deficit per flow + */ +class FqCoDelQueueDiscDeficit : public TestCase +{ +public: + FqCoDelQueueDiscDeficit (); + virtual ~FqCoDelQueueDiscDeficit (); + +private: + virtual void DoRun (void); + void AddPacket (Ptr queue, Ipv4Header hdr); +}; + +FqCoDelQueueDiscDeficit::FqCoDelQueueDiscDeficit () + : TestCase ("Test credits and flows status") +{ +} + +FqCoDelQueueDiscDeficit::~FqCoDelQueueDiscDeficit () +{ +} + +void +FqCoDelQueueDiscDeficit::AddPacket (Ptr queue, Ipv4Header hdr) +{ + Ptr p = Create (100); + Address dest; + Ptr item = Create (p, dest, 0, hdr); + queue->Enqueue (item); +} + +void +FqCoDelQueueDiscDeficit::DoRun (void) +{ + Ptr queueDisc = CreateObjectWithAttributes (); + Ptr ipv6Filter = CreateObject (); + Ptr ipv4Filter = CreateObject (); + queueDisc->AddPacketFilter (ipv6Filter); + queueDisc->AddPacketFilter (ipv4Filter); + + queueDisc->SetQuantum (90); + queueDisc->Initialize (); + + Ipv4Header hdr; + hdr.SetPayloadSize (100); + hdr.SetSource (Ipv4Address ("10.10.1.1")); + hdr.SetDestination (Ipv4Address ("10.10.1.2")); + hdr.SetProtocol (7); + + // Add a packet from the first flow + AddPacket (queueDisc, hdr); + NS_TEST_ASSERT_MSG_EQ (queueDisc->QueueDisc::GetNPackets (), 1, "unexpected number of packets in the queue disc"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (0)->GetQueueDisc ()->GetNPackets (), 1, "unexpected number of packets in the first flow queue"); + Ptr flow1 = StaticCast (queueDisc->GetQueueDiscClass (0)); + NS_TEST_ASSERT_MSG_EQ (flow1->GetDeficit (), static_cast (queueDisc->GetQuantum ()), "the deficit of the first flow must equal the quantum"); + NS_TEST_ASSERT_MSG_EQ (flow1->GetStatus (), FqCoDelFlow::NEW_FLOW, "the first flow must be in the list of new queues"); + // Dequeue a packet + queueDisc->Dequeue (); + NS_TEST_ASSERT_MSG_EQ (queueDisc->QueueDisc::GetNPackets (), 0, "unexpected number of packets in the queue disc"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (0)->GetQueueDisc ()->GetNPackets (), 0, "unexpected number of packets in the first flow queue"); + // the deficit for the first flow becomes 90 - (100+20) = -30 + NS_TEST_ASSERT_MSG_EQ (flow1->GetDeficit (), -30, "unexpected deficit for the first flow"); + + // Add two packets from the first flow + AddPacket (queueDisc, hdr); + AddPacket (queueDisc, hdr); + NS_TEST_ASSERT_MSG_EQ (queueDisc->QueueDisc::GetNPackets (), 2, "unexpected number of packets in the queue disc"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (0)->GetQueueDisc ()->GetNPackets (), 2, "unexpected number of packets in the first flow queue"); + NS_TEST_ASSERT_MSG_EQ (flow1->GetStatus (), FqCoDelFlow::NEW_FLOW, "the first flow must still be in the list of new queues"); + + // Add two packets from the second flow + hdr.SetDestination (Ipv4Address ("10.10.1.10")); + AddPacket (queueDisc, hdr); + AddPacket (queueDisc, hdr); + NS_TEST_ASSERT_MSG_EQ (queueDisc->QueueDisc::GetNPackets (), 4, "unexpected number of packets in the queue disc"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (0)->GetQueueDisc ()->GetNPackets (), 2, "unexpected number of packets in the first flow queue"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (1)->GetQueueDisc ()->GetNPackets (), 2, "unexpected number of packets in the second flow queue"); + Ptr flow2 = StaticCast (queueDisc->GetQueueDiscClass (1)); + NS_TEST_ASSERT_MSG_EQ (flow2->GetDeficit (), static_cast (queueDisc->GetQuantum ()), "the deficit of the second flow must equal the quantum"); + NS_TEST_ASSERT_MSG_EQ (flow2->GetStatus (), FqCoDelFlow::NEW_FLOW, "the second flow must be in the list of new queues"); + + // Dequeue a packet (from the second flow, as the first flow has a negative deficit) + queueDisc->Dequeue (); + NS_TEST_ASSERT_MSG_EQ (queueDisc->QueueDisc::GetNPackets (), 3, "unexpected number of packets in the queue disc"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (0)->GetQueueDisc ()->GetNPackets (), 2, "unexpected number of packets in the first flow queue"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (1)->GetQueueDisc ()->GetNPackets (), 1, "unexpected number of packets in the second flow queue"); + // the first flow got a quantum of deficit (-30+90=60) and has been moved to the end of the list of old queues + NS_TEST_ASSERT_MSG_EQ (flow1->GetDeficit (), 60, "unexpected deficit for the first flow"); + NS_TEST_ASSERT_MSG_EQ (flow1->GetStatus (), FqCoDelFlow::OLD_FLOW, "the first flow must be in the list of old queues"); + // the second flow has a negative deficit (-30) and is still in the list of new queues + NS_TEST_ASSERT_MSG_EQ (flow2->GetDeficit (), -30, "unexpected deficit for the second flow"); + NS_TEST_ASSERT_MSG_EQ (flow2->GetStatus (), FqCoDelFlow::NEW_FLOW, "the second flow must be in the list of new queues"); + + // Dequeue a packet (from the first flow, as the second flow has a negative deficit) + queueDisc->Dequeue (); + NS_TEST_ASSERT_MSG_EQ (queueDisc->QueueDisc::GetNPackets (), 2, "unexpected number of packets in the queue disc"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (0)->GetQueueDisc ()->GetNPackets (), 1, "unexpected number of packets in the first flow queue"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (1)->GetQueueDisc ()->GetNPackets (), 1, "unexpected number of packets in the second flow queue"); + // the first flow has a negative deficit (60-(100+20)= -60) and stays in the list of old queues + NS_TEST_ASSERT_MSG_EQ (flow1->GetDeficit (), -60, "unexpected deficit for the first flow"); + NS_TEST_ASSERT_MSG_EQ (flow1->GetStatus (), FqCoDelFlow::OLD_FLOW, "the first flow must be in the list of old queues"); + // the second flow got a quantum of deficit (-30+90=60) and has been moved to the end of the list of old queues + NS_TEST_ASSERT_MSG_EQ (flow2->GetDeficit (), 60, "unexpected deficit for the second flow"); + NS_TEST_ASSERT_MSG_EQ (flow2->GetStatus (), FqCoDelFlow::OLD_FLOW, "the second flow must be in the list of new queues"); + + // Dequeue a packet (from the second flow, as the first flow has a negative deficit) + queueDisc->Dequeue (); + NS_TEST_ASSERT_MSG_EQ (queueDisc->QueueDisc::GetNPackets (), 1, "unexpected number of packets in the queue disc"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (0)->GetQueueDisc ()->GetNPackets (), 1, "unexpected number of packets in the first flow queue"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (1)->GetQueueDisc ()->GetNPackets (), 0, "unexpected number of packets in the second flow queue"); + // the first flow got a quantum of deficit (-60+90=30) and has been moved to the end of the list of old queues + NS_TEST_ASSERT_MSG_EQ (flow1->GetDeficit (), 30, "unexpected deficit for the first flow"); + NS_TEST_ASSERT_MSG_EQ (flow1->GetStatus (), FqCoDelFlow::OLD_FLOW, "the first flow must be in the list of old queues"); + // the second flow has a negative deficit (60-(100+20)= -60) + NS_TEST_ASSERT_MSG_EQ (flow2->GetDeficit (), -60, "unexpected deficit for the second flow"); + NS_TEST_ASSERT_MSG_EQ (flow2->GetStatus (), FqCoDelFlow::OLD_FLOW, "the second flow must be in the list of new queues"); + + // Dequeue a packet (from the first flow, as the second flow has a negative deficit) + queueDisc->Dequeue (); + NS_TEST_ASSERT_MSG_EQ (queueDisc->QueueDisc::GetNPackets (), 0, "unexpected number of packets in the queue disc"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (0)->GetQueueDisc ()->GetNPackets (), 0, "unexpected number of packets in the first flow queue"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (1)->GetQueueDisc ()->GetNPackets (), 0, "unexpected number of packets in the second flow queue"); + // the first flow has a negative deficit (30-(100+20)= -90) + NS_TEST_ASSERT_MSG_EQ (flow1->GetDeficit (), -90, "unexpected deficit for the first flow"); + NS_TEST_ASSERT_MSG_EQ (flow1->GetStatus (), FqCoDelFlow::OLD_FLOW, "the first flow must be in the list of old queues"); + // the second flow got a quantum of deficit (-60+90=30) and has been moved to the end of the list of old queues + NS_TEST_ASSERT_MSG_EQ (flow2->GetDeficit (), 30, "unexpected deficit for the second flow"); + NS_TEST_ASSERT_MSG_EQ (flow2->GetStatus (), FqCoDelFlow::OLD_FLOW, "the second flow must be in the list of new queues"); + + // Dequeue a packet + queueDisc->Dequeue (); + // the first flow is at the head of the list of old queues but has a negative deficit, thus it gets a quantun + // of deficit (-90+90=0) and is moved to the end of the list of old queues. Then, the second flow (which has a + // positive deficit) is selected, but the second flow is empty and thus it is set to inactive. The first flow is + // reconsidered, but it has a null deficit, hence it gets another quantum of deficit (0+90=90). Then, the first + // flow is reconsidered again, now it has a positive deficit and hence it is selected. But, it is empty and + // therefore is set to inactive, too. + NS_TEST_ASSERT_MSG_EQ (flow1->GetDeficit (), 90, "unexpected deficit for the first flow"); + NS_TEST_ASSERT_MSG_EQ (flow1->GetStatus (), FqCoDelFlow::INACTIVE, "the first flow must be inactive"); + NS_TEST_ASSERT_MSG_EQ (flow2->GetDeficit (), 30, "unexpected deficit for the second flow"); + NS_TEST_ASSERT_MSG_EQ (flow2->GetStatus (), FqCoDelFlow::INACTIVE, "the second flow must be inactive"); +} + +/** + * This class tests the TCP flows separation + */ +class FqCoDelQueueDiscTCPFlowsSeparation : public TestCase +{ +public: + FqCoDelQueueDiscTCPFlowsSeparation (); + virtual ~FqCoDelQueueDiscTCPFlowsSeparation (); + +private: + virtual void DoRun (void); + void AddPacket (Ptr queue, Ipv4Header ipHdr, TcpHeader tcpHdr); +}; + +FqCoDelQueueDiscTCPFlowsSeparation::FqCoDelQueueDiscTCPFlowsSeparation () + : TestCase ("Test TCP flows separation") +{ +} + +FqCoDelQueueDiscTCPFlowsSeparation::~FqCoDelQueueDiscTCPFlowsSeparation () +{ +} + +void +FqCoDelQueueDiscTCPFlowsSeparation::AddPacket (Ptr queue, Ipv4Header ipHdr, TcpHeader tcpHdr) +{ + Ptr p = Create (100); + p->AddHeader (tcpHdr); + Address dest; + Ptr item = Create (p, dest, 0, ipHdr); + queue->Enqueue (item); +} + +void +FqCoDelQueueDiscTCPFlowsSeparation::DoRun (void) +{ + Ptr queueDisc = CreateObjectWithAttributes ("Packet limit", UintegerValue (10)); + Ptr ipv6Filter = CreateObject (); + Ptr ipv4Filter = CreateObject (); + queueDisc->AddPacketFilter (ipv6Filter); + queueDisc->AddPacketFilter (ipv4Filter); + + queueDisc->SetQuantum (1500); + queueDisc->Initialize (); + + Ipv4Header hdr; + hdr.SetPayloadSize (100); + hdr.SetSource (Ipv4Address ("10.10.1.1")); + hdr.SetDestination (Ipv4Address ("10.10.1.2")); + hdr.SetProtocol (6); + + TcpHeader tcpHdr; + tcpHdr.SetSourcePort (7); + tcpHdr.SetDestinationPort (27); + + // Add three packets from the first flow + AddPacket (queueDisc, hdr, tcpHdr); + AddPacket (queueDisc, hdr, tcpHdr); + AddPacket (queueDisc, hdr, tcpHdr); + NS_TEST_ASSERT_MSG_EQ (queueDisc->QueueDisc::GetNPackets (), 3, "unexpected number of packets in the queue disc"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (0)->GetQueueDisc ()->GetNPackets (), 3, "unexpected number of packets in the first flow queue"); + + // Add a packet from the second flow + tcpHdr.SetSourcePort (8); + AddPacket (queueDisc, hdr, tcpHdr); + NS_TEST_ASSERT_MSG_EQ (queueDisc->QueueDisc::GetNPackets (), 4, "unexpected number of packets in the queue disc"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (0)->GetQueueDisc ()->GetNPackets (), 3, "unexpected number of packets in the first flow queue"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (1)->GetQueueDisc ()->GetNPackets (), 1, "unexpected number of packets in the second flow queue"); + + // Add a packet from the third flow + tcpHdr.SetDestinationPort (28); + AddPacket (queueDisc, hdr, tcpHdr); + NS_TEST_ASSERT_MSG_EQ (queueDisc->QueueDisc::GetNPackets (), 5, "unexpected number of packets in the queue disc"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (0)->GetQueueDisc ()->GetNPackets (), 3, "unexpected number of packets in the first flow queue"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (1)->GetQueueDisc ()->GetNPackets (), 1, "unexpected number of packets in the second flow queue"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (2)->GetQueueDisc ()->GetNPackets (), 1, "unexpected number of packets in the third flow queue"); + + // Add two packets from the fourth flow + tcpHdr.SetSourcePort (7); + AddPacket (queueDisc, hdr, tcpHdr); + AddPacket (queueDisc, hdr, tcpHdr); + NS_TEST_ASSERT_MSG_EQ (queueDisc->QueueDisc::GetNPackets (), 7, "unexpected number of packets in the queue disc"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (0)->GetQueueDisc ()->GetNPackets (), 3, "unexpected number of packets in the first flow queue"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (1)->GetQueueDisc ()->GetNPackets (), 1, "unexpected number of packets in the second flow queue"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (2)->GetQueueDisc ()->GetNPackets (), 1, "unexpected number of packets in the third flow queue"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (3)->GetQueueDisc ()->GetNPackets (), 2, "unexpected number of packets in the third flow queue"); +} + +/** + * This class tests the UDP flows separation + */ +class FqCoDelQueueDiscUDPFlowsSeparation : public TestCase +{ +public: + FqCoDelQueueDiscUDPFlowsSeparation (); + virtual ~FqCoDelQueueDiscUDPFlowsSeparation (); + +private: + virtual void DoRun (void); + void AddPacket (Ptr queue, Ipv4Header ipHdr, UdpHeader udpHdr); +}; + +FqCoDelQueueDiscUDPFlowsSeparation::FqCoDelQueueDiscUDPFlowsSeparation () + : TestCase ("Test UDP flows separation") +{ +} + +FqCoDelQueueDiscUDPFlowsSeparation::~FqCoDelQueueDiscUDPFlowsSeparation () +{ +} + +void +FqCoDelQueueDiscUDPFlowsSeparation::AddPacket (Ptr queue, Ipv4Header ipHdr, UdpHeader udpHdr) +{ + Ptr p = Create (100); + p->AddHeader (udpHdr); + Address dest; + Ptr item = Create (p, dest, 0, ipHdr); + queue->Enqueue (item); +} + +void +FqCoDelQueueDiscUDPFlowsSeparation::DoRun (void) +{ + Ptr queueDisc = CreateObjectWithAttributes ("Packet limit", UintegerValue (10)); + Ptr ipv6Filter = CreateObject (); + Ptr ipv4Filter = CreateObject (); + queueDisc->AddPacketFilter (ipv6Filter); + queueDisc->AddPacketFilter (ipv4Filter); + + queueDisc->SetQuantum (1500); + queueDisc->Initialize (); + + Ipv4Header hdr; + hdr.SetPayloadSize (100); + hdr.SetSource (Ipv4Address ("10.10.1.1")); + hdr.SetDestination (Ipv4Address ("10.10.1.2")); + hdr.SetProtocol (17); + + UdpHeader udpHdr; + udpHdr.SetSourcePort (7); + udpHdr.SetDestinationPort (27); + + // Add three packets from the first flow + AddPacket (queueDisc, hdr, udpHdr); + AddPacket (queueDisc, hdr, udpHdr); + AddPacket (queueDisc, hdr, udpHdr); + NS_TEST_ASSERT_MSG_EQ (queueDisc->QueueDisc::GetNPackets (), 3, "unexpected number of packets in the queue disc"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (0)->GetQueueDisc ()->GetNPackets (), 3, "unexpected number of packets in the first flow queue"); + + // Add a packet from the second flow + udpHdr.SetSourcePort (8); + AddPacket (queueDisc, hdr, udpHdr); + NS_TEST_ASSERT_MSG_EQ (queueDisc->QueueDisc::GetNPackets (), 4, "unexpected number of packets in the queue disc"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (0)->GetQueueDisc ()->GetNPackets (), 3, "unexpected number of packets in the first flow queue"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (1)->GetQueueDisc ()->GetNPackets (), 1, "unexpected number of packets in the second flow queue"); + + // Add a packet from the third flow + udpHdr.SetDestinationPort (28); + AddPacket (queueDisc, hdr, udpHdr); + NS_TEST_ASSERT_MSG_EQ (queueDisc->QueueDisc::GetNPackets (), 5, "unexpected number of packets in the queue disc"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (0)->GetQueueDisc ()->GetNPackets (), 3, "unexpected number of packets in the first flow queue"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (1)->GetQueueDisc ()->GetNPackets (), 1, "unexpected number of packets in the second flow queue"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (2)->GetQueueDisc ()->GetNPackets (), 1, "unexpected number of packets in the third flow queue"); + + // Add two packets from the fourth flow + udpHdr.SetSourcePort (7); + AddPacket (queueDisc, hdr, udpHdr); + AddPacket (queueDisc, hdr, udpHdr); + NS_TEST_ASSERT_MSG_EQ (queueDisc->QueueDisc::GetNPackets (), 7, "unexpected number of packets in the queue disc"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (0)->GetQueueDisc ()->GetNPackets (), 3, "unexpected number of packets in the first flow queue"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (1)->GetQueueDisc ()->GetNPackets (), 1, "unexpected number of packets in the second flow queue"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (2)->GetQueueDisc ()->GetNPackets (), 1, "unexpected number of packets in the third flow queue"); + NS_TEST_ASSERT_MSG_EQ (queueDisc->GetQueueDiscClass (3)->GetQueueDisc ()->GetNPackets (), 2, "unexpected number of packets in the third flow queue"); +} + +class FqCoDelQueueDiscTestSuite : public TestSuite +{ +public: + FqCoDelQueueDiscTestSuite (); +}; + +FqCoDelQueueDiscTestSuite::FqCoDelQueueDiscTestSuite () + : TestSuite ("fq-codel-queue-disc", UNIT) +{ + AddTestCase (new FqCoDelQueueDiscNoSuitableFilter, TestCase::QUICK); + AddTestCase (new FqCoDelQueueDiscIPFlowsSeparationAndPacketLimit, TestCase::QUICK); + AddTestCase (new FqCoDelQueueDiscDeficit, TestCase::QUICK); + AddTestCase (new FqCoDelQueueDiscTCPFlowsSeparation, TestCase::QUICK); + AddTestCase (new FqCoDelQueueDiscUDPFlowsSeparation, TestCase::QUICK); +} + +static FqCoDelQueueDiscTestSuite fqCoDelQueueDiscTestSuite; diff --git a/src/test/wscript b/src/test/wscript index ced076d61..24e149e2b 100644 --- a/src/test/wscript +++ b/src/test/wscript @@ -31,6 +31,7 @@ def build(bld): test_test.source = [ 'csma-system-test-suite.cc', 'ns3tc/adaptive-red-queue-disc-test-suite.cc', + 'ns3tc/fq-codel-queue-disc-test-suite.cc', 'ns3tc/pfifo-fast-queue-disc-test-suite.cc', 'ns3tcp/ns3tcp-cwnd-test-suite.cc', 'ns3tcp/ns3tcp-interop-test-suite.cc', diff --git a/src/traffic-control/doc/fq-codel.rst b/src/traffic-control/doc/fq-codel.rst new file mode 100644 index 000000000..0621ba867 --- /dev/null +++ b/src/traffic-control/doc/fq-codel.rst @@ -0,0 +1,112 @@ +.. include:: replace.txt +.. highlight:: cpp +.. highlight:: bash + +FqCoDel queue disc +------------------ + +This chapter describes the FqCoDel ([Hoe16]_) queue disc implementation in |ns3|. + +The FlowQueue-CoDel (FQ-CoDel) algorithm is a combined packet scheduler and +Active Queue Management (AQM) algorithm developed as part of the +bufferbloat-fighting community effort ([Buf16]_). +FqCoDel classifies incoming packets into different queues (by default, 1024 +queues are created), which are served according to a modified Deficit Round +Robin (DRR) queue scheduler. Each queue is managed by the CoDel AQM algorithm. +FqCoDel distinguishes between "new" queues (which don't build up a standing +queue) and "old" queues, that have queued enough data to be around for more +than one iteration of the round-robin scheduler. + + +Model Description +***************** + +The source code for the FqCoDel queue disc is located in the directory +``src/traffic-control/model`` and consists of 2 files `fq-codel-queue-disc.h` +and `fq-codel-queue-disc.cc` defining a FqCoDelQueueDisc class and a helper +FqCoDelFlow class. The code was ported to |ns3| based on Linux kernel code +implemented by Eric Dumazet. + +* class :cpp:class:`FqCoDelQueueDisc`: This class implements the main FqCoDel algorithm: + + * ``FqCoDelQueueDisc::DoEnqueue ()``: This routine uses the configured packet filters to classify the given packet into an appropriate queue. If the filters are unable to classify the packet, the packet is dropped. Otherwise, it is handed over to the CoDel algorithm for timestamping. Then, if the queue is not currently active (i.e., if it is not in either the list of new or the list of old queues), it is added to the end of the list of new queues, and its deficit is initiated to the configured quantum. Otherwise, the queue is left in its current queue list. Finally, the total number of enqueued packets is compared with the configured limit, and if it is above this value (which can happen since a packet was just enqueued), packets are dropped from the head of the queue with the largest current byte count until the number of dropped packets reaches the configured drop batch size or the backlog of the queue has been halved. Note that this in most cases means that the packet that was just enqueued is not among the packets that get dropped, which may even be from a different queue. + + * ``FqCoDelQueueDisc::DoDequeue ()``: The first task performed by this routine is selecting a queue from which to dequeue a packet. To this end, the scheduler first looks at the list of new queues; for the queue at the head of that list, if that queue has a negative deficit (i.e., it has already dequeued at least a quantum of bytes), it is given an additional amount of deficit, the queue is put onto the end of the list of old queues, and the routine selects the next queue and starts again. Otherwise, that queue is selected for dequeue. If the list of new queues is empty, the scheduler proceeds down the list of old queues in the same fashion (checking the deficit, and either selecting the queue for dequeuing, or increasing deficit and putting the queue back at the end of the list). After having selected a queue from which to dequeue a packet, the CoDel algorithm is invoked on that queue. As a result of this, one or more packets may be discarded from the head of the selected queue, before the packet that should be dequeued is returned (or nothing is returned if the queue is or becomes empty while being handled by the CoDel algorithm). Finally, if the CoDel algorithm does not return a packet, then the queue must be empty, and the scheduler does one of two things: if the queue selected for dequeue came from the list of new queues, it is moved to the end of the list of old queues. If instead it came from the list of old queues, that queue is removed from the list, to be added back (as a new queue) the next time a packet for that queue arrives. Then (since no packet was available for dequeue), the whole dequeue process is restarted from the beginning. If, instead, the scheduler did get a packet back from the CoDel algorithm, it subtracts the size of the packet from the byte deficit for the selected queue and returns the packet as the result of the dequeue operation. + + * ``FqCoDelQueueDisc::FqCoDelDrop ()``: This routine is invoked by ``FqCoDelQueueDisc::DoEnqueue()`` to drop packets from the head of the queue with the largest current byte count. This routine keeps dropping packets until the number of dropped packets reaches the configured drop batch size or the backlog of the queue has been halved. + +* class :cpp:class:`FqCoDelFlow`: This class implements a flow queue, by keeping its current status (whether it is in the list of new queues, in the list of old queues or inactive) and its current deficit. + +In Linux, by default, packet classification is done by hashing (using a Jenkins +hash function) on the 5-tuple of IP protocol, and source and destination IP +addresses and port numbers (if they exist), and taking the hash value modulo +the number of queues. The hash is salted by modulo addition of a random value +selected at initialisation time, to prevent possible DoS attacks if the hash +is predictable ahead of time. Alternatively, any other packet filter can be +configured. +In |ns3|, at least one packet filter must be added to an FqCoDel queue disc. +The Linux default classifier is provided via the FqCoDelIpv{4,6}PacketFilter classes. +Finally, neither internal queues nor classes can be configured for an FqCoDel +queue disc. + + +References +========== + +.. [Hoe16] T. Hoeiland-Joergensen, P. McKenney, D. Taht, J. Gettys and E. Dumazet, The FlowQueue-CoDel Packet Scheduler and Active Queue Management Algorithm, IETF draft. Available online at ``_ + +.. [Buf16] Bufferbloat.net. Available online at ``_. + + +Attributes +========== + +The key attributes that the FqCoDelQueue class holds include the following: + +* ``Interval:`` The interval parameter to be used on the CoDel queues. The default value is 100 ms. +* ``Target:`` The target parameter to be used on the CoDel queues. The default value is 5 ms. +* ``Packet limit:`` The limit on the maximum number of packets stored by FqCoDel. +* ``Flows:`` The number of flow queues managed by FqCoDel. +* ``DropBatchSize:`` The maximum number of packets dropped from the fat flow. + +Note that the quantum, i.e., the number of bytes each queue gets to dequeue on +each round of the scheduling algorithm, is set by default to the MTU size of the +device (at initialisation time). The ``FqCoDelQueueDisc::SetQuantum ()`` method +can be used (at any time) to configure a different value. + +Examples +======== + +A typical usage pattern is to create a traffic control helper and to configure type +and attributes of queue disc and filters from the helper. For example, FqCodel +can be configured as follows: + +.. sourcecode:: cpp + + TrafficControlHelper tch; + uint16_t handle = tch.SetRootQueueDisc ("ns3::FqCoDelQueueDisc", "DropBatchSize", UintegerValue (1)); + tch.AddPacketFilter (handle, "ns3::FqCoDelIpv4PacketFilter", "Perturbation", UintegerValue (256)); + tch.AddPacketFilter (handle, "ns3::FqCoDelIpv6PacketFilter"); + QueueDiscContainer qdiscs = tch.Install (devices); + +Validation +********** + +The FqCoDel model is tested using :cpp:class:`FqCoDelQueueDiscTestSuite` class defined in `src/test/ns3tc/codel-queue-test-suite.cc`. The suite includes 5 test cases: + +* Test 1: The first test checks that packets that cannot be classified by any available filter are dropped. +* Test 2: The second test checks that IPv4 packets having distinct destination addresses are enqueued into different flow queues. Also, it checks that packets are dropped from the fat flow in case the queue disc capacity is exceeded. +* Test 3: The third test checks the dequeue operation and the deficit round robin-based scheduler. +* Test 4: The fourth test checks that TCP packets with distinct port numbers are enqueued into different flow queues. +* Test 5: The fifth test checks that UDP packets with distinct port numbers are enqueued into different flow queues. + +The test suite can be run using the following commands:: + + $ ./waf configure --enable-examples --enable-tests + $ ./waf build + $ ./test.py -s fq-codel-queue-disc + +or:: + + $ NS_LOG="FqCoDelQueueDisc" ./waf --run "test-runner --suite=fq-codel-queue-disc" + diff --git a/src/traffic-control/model/fq-codel-queue-disc.cc b/src/traffic-control/model/fq-codel-queue-disc.cc new file mode 100644 index 000000000..06fd2ca0b --- /dev/null +++ b/src/traffic-control/model/fq-codel-queue-disc.cc @@ -0,0 +1,396 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2016 Universita' degli Studi di Napoli Federico II + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Authors: Pasquale Imputato + * Stefano Avallone +*/ + +#include "ns3/log.h" +#include "ns3/string.h" +#include "fq-codel-queue-disc.h" + +namespace ns3 { + +NS_LOG_COMPONENT_DEFINE ("FqCoDelQueueDisc"); + +NS_OBJECT_ENSURE_REGISTERED (FqCoDelFlow); + +TypeId FqCoDelFlow::GetTypeId (void) +{ + static TypeId tid = TypeId ("ns3::FqCoDelFlow") + .SetParent () + .SetGroupName ("TrafficControl") + .AddConstructor () + ; + return tid; +} + +FqCoDelFlow::FqCoDelFlow () + : m_deficit (0), + m_status (INACTIVE) +{ + NS_LOG_FUNCTION (this); +} + +FqCoDelFlow::~FqCoDelFlow () +{ + NS_LOG_FUNCTION (this); +} + +void +FqCoDelFlow::SetDeficit (uint32_t deficit) +{ + NS_LOG_FUNCTION (this << deficit); + m_deficit = deficit; +} + +int32_t +FqCoDelFlow::GetDeficit (void) const +{ + NS_LOG_FUNCTION (this); + return m_deficit; +} + +void +FqCoDelFlow::IncreaseDeficit (int32_t deficit) +{ + NS_LOG_FUNCTION (this << deficit); + m_deficit += deficit; +} + +void +FqCoDelFlow::SetStatus (FlowStatus status) +{ + NS_LOG_FUNCTION (this); + m_status = status; +} + +FqCoDelFlow::FlowStatus +FqCoDelFlow::GetStatus (void) const +{ + NS_LOG_FUNCTION (this); + return m_status; +} + + +NS_OBJECT_ENSURE_REGISTERED (FqCoDelQueueDisc); + +TypeId FqCoDelQueueDisc::GetTypeId (void) +{ + static TypeId tid = TypeId ("ns3::FqCoDelQueueDisc") + .SetParent () + .SetGroupName ("TrafficControl") + .AddConstructor () + .AddAttribute ("Interval", + "The CoDel algorithm interval for each FQCoDel queue", + StringValue ("100ms"), + MakeStringAccessor (&FqCoDelQueueDisc::m_interval), + MakeStringChecker ()) + .AddAttribute ("Target", + "The CoDel algorithm target queue delay for each FQCoDel queue", + StringValue ("5ms"), + MakeStringAccessor (&FqCoDelQueueDisc::m_target), + MakeStringChecker ()) + .AddAttribute ("Packet limit", + "The hard limit on the real queue size, measured in packets", + UintegerValue (10 * 1024), + MakeUintegerAccessor (&FqCoDelQueueDisc::m_limit), + MakeUintegerChecker ()) + .AddAttribute ("Flows", + "The number of queues into which the incoming packets are classified", + UintegerValue (1024), + MakeUintegerAccessor (&FqCoDelQueueDisc::m_flows), + MakeUintegerChecker ()) + .AddAttribute ("DropBatchSize", + "The maximum number of packets dropped from the fat flow", + UintegerValue (64), + MakeUintegerAccessor (&FqCoDelQueueDisc::m_dropBatchSize), + MakeUintegerChecker ()) + ; + return tid; +} + +FqCoDelQueueDisc::FqCoDelQueueDisc () + : m_quantum (0), + m_overlimitDroppedPackets (0) +{ + NS_LOG_FUNCTION (this); +} + +FqCoDelQueueDisc::~FqCoDelQueueDisc () +{ + NS_LOG_FUNCTION (this); +} + +void +FqCoDelQueueDisc::SetQuantum (uint32_t quantum) +{ + NS_LOG_FUNCTION (this << quantum); + m_quantum = quantum; +} + +uint32_t +FqCoDelQueueDisc::GetQuantum (void) const +{ + return m_quantum; +} + +bool +FqCoDelQueueDisc::DoEnqueue (Ptr item) +{ + NS_LOG_FUNCTION (this << item); + + int32_t ret = Classify (item); + + if (ret == PacketFilter::PF_NO_MATCH) + { + NS_LOG_ERROR ("No filter has been able to classify this packet, drop it."); + Drop (item); + return false; + } + + uint32_t h = ret % m_flows; + + Ptr flow; + if (m_flowsIndices.find (h) == m_flowsIndices.end ()) + { + NS_LOG_DEBUG ("Creating a new flow queue with index " << h); + flow = m_flowFactory.Create (); + Ptr qd = m_queueDiscFactory.Create (); + qd->Initialize (); + flow->SetQueueDisc (qd); + AddQueueDiscClass (flow); + + m_flowsIndices[h] = GetNQueueDiscClasses () - 1; + } + else + { + flow = StaticCast (GetQueueDiscClass (m_flowsIndices[h])); + } + + if (flow->GetStatus () == FqCoDelFlow::INACTIVE) + { + flow->SetStatus (FqCoDelFlow::NEW_FLOW); + flow->SetDeficit (m_quantum); + m_newFlows.push_back (flow); + } + + flow->GetQueueDisc ()->Enqueue (item); + + NS_LOG_DEBUG ("Packet enqueued into flow " << h << "; flow index " << m_flowsIndices[h]); + + if (GetNPackets () > m_limit) + { + FqCoDelDrop (); + } + + return true; +} + +Ptr +FqCoDelQueueDisc::DoDequeue (void) +{ + NS_LOG_FUNCTION (this); + + Ptr flow; + Ptr item; + + do + { + bool found = false; + + while (!found && !m_newFlows.empty ()) + { + flow = m_newFlows.front (); + + if (flow->GetDeficit () <= 0) + { + flow->IncreaseDeficit (m_quantum); + flow->SetStatus (FqCoDelFlow::OLD_FLOW); + m_oldFlows.push_back (flow); + m_newFlows.pop_front (); + } + else + { + NS_LOG_DEBUG ("Found a new flow with positive deficit"); + found = true; + } + } + + while (!found && !m_oldFlows.empty ()) + { + flow = m_oldFlows.front (); + + if (flow->GetDeficit () <= 0) + { + flow->IncreaseDeficit (m_quantum); + m_oldFlows.push_back (flow); + m_oldFlows.pop_front (); + } + else + { + NS_LOG_DEBUG ("Found an old flow with positive deficit"); + found = true; + } + } + + if (!found) + { + NS_LOG_DEBUG ("No flow found to dequeue a packet"); + return 0; + } + + item = flow->GetQueueDisc ()->Dequeue (); + + if (!item) + { + NS_LOG_DEBUG ("Could not get a packet from the selected flow queue"); + if (!m_newFlows.empty ()) + { + flow->SetStatus (FqCoDelFlow::OLD_FLOW); + m_oldFlows.push_back (flow); + m_newFlows.pop_front (); + } + else + { + flow->SetStatus (FqCoDelFlow::INACTIVE); + m_oldFlows.pop_front (); + } + } + else + { + NS_LOG_DEBUG ("Dequeued packet " << item->GetPacket ()); + } + } while (item == 0); + + flow->IncreaseDeficit (-item->GetPacketSize ()); + + return item; +} + +Ptr +FqCoDelQueueDisc::DoPeek (void) const +{ + NS_LOG_FUNCTION (this); + + Ptr flow; + + if (!m_newFlows.empty ()) + { + flow = m_newFlows.front (); + } + else + { + if (!m_oldFlows.empty ()) + { + flow = m_oldFlows.front (); + } + else + { + return 0; + } + } + + return flow->GetQueueDisc ()->Peek (); +} + +bool +FqCoDelQueueDisc::CheckConfig (void) +{ + NS_LOG_FUNCTION (this); + if (GetNQueueDiscClasses () > 0) + { + NS_LOG_ERROR ("FqCoDelQueueDisc cannot have classes"); + return false; + } + + if (GetNPacketFilters () == 0) + { + NS_LOG_ERROR ("FqCoDelQueueDisc needs at least a packet filter"); + return false; + } + + if (GetNInternalQueues () > 0) + { + NS_LOG_ERROR ("FqCoDelQueueDisc cannot have internal queues"); + return false; + } + + return true; +} + +void +FqCoDelQueueDisc::InitializeParams (void) +{ + NS_LOG_FUNCTION (this); + + // we are at initialization time. If the user has not set a quantum value, + // set the quantum to the MTU of the device + if (!m_quantum) + { + Ptr device = GetNetDevice (); + NS_ASSERT_MSG (device, "Device not set for the queue disc"); + m_quantum = device->GetMtu (); + NS_LOG_DEBUG ("Setting the quantum to the MTU of the device: " << m_quantum); + } + + m_flowFactory.SetTypeId ("ns3::FqCoDelFlow"); + + m_queueDiscFactory.SetTypeId ("ns3::CoDelQueueDisc"); + m_queueDiscFactory.Set ("Mode", EnumValue (Queue::QUEUE_MODE_PACKETS)); + m_queueDiscFactory.Set ("MaxPackets", UintegerValue (m_limit + 1)); + m_queueDiscFactory.Set ("Interval", StringValue (m_interval)); + m_queueDiscFactory.Set ("Target", StringValue (m_target)); +} + +uint32_t +FqCoDelQueueDisc::FqCoDelDrop (void) +{ + NS_LOG_FUNCTION (this); + + uint32_t maxBacklog = 0, index = 0; + Ptr qd; + + /* Queue is full! Find the fat flow and drop packet(s) from it */ + for (uint32_t i = 0; i < GetNQueueDiscClasses (); i++) + { + qd = GetQueueDiscClass (i)->GetQueueDisc (); + uint32_t bytes = qd->GetNBytes (); + if (bytes > maxBacklog) + { + maxBacklog = bytes; + index = i; + } + } + + /* Our goal is to drop half of this fat flow backlog */ + uint32_t len = 0, count = 0, threshold = maxBacklog >> 1; + qd = GetQueueDiscClass (index)->GetQueueDisc (); + Ptr item; + + do + { + item = qd->GetInternalQueue (0)->Remove (); + len += item->GetPacketSize (); + } while (++count < m_dropBatchSize && len < threshold); + + m_overlimitDroppedPackets += count; + + return index; +} + +} // namespace ns3 diff --git a/src/traffic-control/model/fq-codel-queue-disc.h b/src/traffic-control/model/fq-codel-queue-disc.h new file mode 100644 index 000000000..079f25235 --- /dev/null +++ b/src/traffic-control/model/fq-codel-queue-disc.h @@ -0,0 +1,162 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2016 Universita' degli Studi di Napoli Federico II + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Authors: Pasquale Imputato + * Stefano Avallone + */ + +#ifndef FQ_CODEL_QUEUE_DISC +#define FQ_CODEL_QUEUE_DISC + +#include "ns3/queue-disc.h" +#include "ns3/object-factory.h" +#include +#include + +namespace ns3 { + +/** + * \ingroup traffic-control + * + * \brief A flow queue used by the FqCoDel queue disc + */ + +class FqCoDelFlow : public QueueDiscClass { +public: + /** + * \brief Get the type ID. + * \return the object TypeId + */ + static TypeId GetTypeId (void); + /** + * \brief FqCoDelFlow constructor + */ + FqCoDelFlow (); + + virtual ~FqCoDelFlow (); + + /** + * \enum FlowStatus + * \brief Used to determine the status of this flow queue + */ + enum FlowStatus + { + INACTIVE, + NEW_FLOW, + OLD_FLOW + }; + + /** + * \brief Set the deficit for this flow + * \param deficit the deficit for this flow + */ + void SetDeficit (uint32_t deficit); + /** + * \brief Get the deficit for this flow + * \return the deficit for this flow + */ + int32_t GetDeficit (void) const; + /** + * \brief Increase the deficit for this flow + * \param deficit the amount by which the deficit is to be increased + */ + void IncreaseDeficit (int32_t deficit); + /** + * \brief Set the status for this flow + * \param status the status for this flow + */ + void SetStatus (FlowStatus status); + /** + * \brief Get the status of this flow + * \return the status of this flow + */ + FlowStatus GetStatus (void) const; + +private: + int32_t m_deficit; //!< the deficit for this flow + FlowStatus m_status; //!< the status of this flow +}; + + +/** + * \ingroup traffic-control + * + * \brief A FqCoDel packet queue disc + */ + +class FqCoDelQueueDisc : public QueueDisc { +public: + /** + * \brief Get the type ID. + * \return the object TypeId + */ + static TypeId GetTypeId (void); + /** + * \brief FqCoDelQueueDisc constructor + */ + FqCoDelQueueDisc (); + + virtual ~FqCoDelQueueDisc (); + + /** + * \brief Set the quantum value. + * + * \param quantum The number of bytes each queue gets to dequeue on each round of the scheduling algorithm + */ + void SetQuantum (uint32_t quantum); + + /** + * \brief Get the quantum value. + * + * \returns The number of bytes each queue gets to dequeue on each round of the scheduling algorithm + */ + uint32_t GetQuantum (void) const; + +private: + virtual bool DoEnqueue (Ptr item); + virtual Ptr DoDequeue (void); + virtual Ptr DoPeek (void) const; + virtual bool CheckConfig (void); + virtual void InitializeParams (void); + + /** + * \brief Drop a packet from the head of the queue with the largest current byte count + * \return the index of the queue with the largest current byte count + */ + uint32_t FqCoDelDrop (void); + + std::string m_interval; //!< CoDel interval attribute + std::string m_target; //!< CoDel target attribute + uint32_t m_limit; //!< Maximum number of packets in the queue disc + uint32_t m_quantum; //!< Deficit assigned to flows at each round + uint32_t m_flows; //!< Number of flow queues + uint32_t m_dropBatchSize; //!< Max number of packets dropped from the fat flow + + uint32_t m_overlimitDroppedPackets; //!< Number of overlimit dropped packets + + std::list > m_newFlows; //!< The list of new flows + std::list > m_oldFlows; //!< The list of old flows + + std::map m_flowsIndices; //!< Map with the index of class for each flow + + ObjectFactory m_flowFactory; //!< Factory to create a new flow + ObjectFactory m_queueDiscFactory; //!< Factory to create a new queue +}; + +} // namespace ns3 + +#endif /* FQ_CODEL_QUEUE_DISC */ diff --git a/src/traffic-control/wscript b/src/traffic-control/wscript index d0897547f..ad8cbc26e 100644 --- a/src/traffic-control/wscript +++ b/src/traffic-control/wscript @@ -15,6 +15,7 @@ def build(bld): 'model/pfifo-fast-queue-disc.cc', 'model/red-queue-disc.cc', 'model/codel-queue-disc.cc', + 'model/fq-codel-queue-disc.cc', 'helper/traffic-control-helper.cc', 'helper/queue-disc-container.cc' ] @@ -34,6 +35,7 @@ def build(bld): 'model/pfifo-fast-queue-disc.h', 'model/red-queue-disc.h', 'model/codel-queue-disc.h', + 'model/fq-codel-queue-disc.h', 'helper/traffic-control-helper.h', 'helper/queue-disc-container.h' ]