tcp: Add DCTCP code and test-suite

This commit is contained in:
Shravya K.S
2018-05-09 16:08:56 +05:30
committed by Tom Henderson
parent c3265b7d44
commit 48bf4e7c29
6 changed files with 1218 additions and 11 deletions

View File

@@ -44,7 +44,7 @@ connection setup and close logic. Several congestion control algorithms
are supported, with NewReno the default, and Westwood, Hybla, HighSpeed,
Vegas, Scalable, Veno, Binary Increase Congestion Control (BIC), Yet Another
HighSpeed TCP (YeAH), Illinois, H-TCP, Low Extra Delay Background Transport
(LEDBAT) and TCP Low Priority (TCP-LP) also supported. The model also supports
(LEDBAT), TCP Low Priority (TCP-LP) and and Data Center TCP (DCTCP) also supported. The model also supports
Selective Acknowledgements (SACK), Proportional Rate Reduction (PRR) and
Explicit Congestion Notification (ECN). Multipath-TCP is not yet supported in
the |ns3| releases.
@@ -782,6 +782,87 @@ phase or not
More information (paper): http://cs.northwestern.edu/~akuzma/rice/doc/TCP-LP.pdf
Data Center TCP (DCTCP)
^^^^^^^^^^^^^^^^^^^^^^^^
DCTCP is an enhancement to the TCP congestion control algorithm for data center
networks and leverages Explicit Congestion Notification (ECN) to provide multi-bit
feedback to the end hosts. DCTCP extends the Explicit Congestion Notification
to estimate the fraction of bytes that encounter congestion, rather than simply
detecting that the congestion has occurred. DCTCP then scales the congestion
window based on this estimate. This approach achieves high burst tolerance, low
latency, and high throughput with shallow-buffered switches.
* Receiver functionality: If CE is set in IP header of incoming packet, send congestion notification to the sender by setting ECE in TCP header.
* Sender functionality: It should maintain an average of fraction of packets marked (α) by using the exponential weighted moving average as shown below:
::
α = (1 - g) x α + g x F
where
* g is the estimation gain (between 0 and 1)
* F is the fraction of packets marked in current RTT.
On receipt of an ACK with ECE bit set, the sender should respond by reducing the congestion
window as follows, once for every window of data:
::
cwnd = cwnd * (1 - α / 2)
Following the recommendation of RFC 8257, the default values of the parameters are:
::
g = 0.0625
alpha (α) = 1
To enable DCTCP on all TCP sockets, the following configuration can be used:
::
Config::SetDefault ("ns3::TcpL4Protocol::SocketType", TypeIdValue (TcpDctcp::GetTypeId ()));
To enable DCTCP on a chosen TCP socket, the following configuration can be used:
::
Config::Set ("$ns3::NodeListPriv/NodeList/1/$ns3::TcpL4Protocol/SocketType", TypeIdValue (TcpDctcp::GetTypeId ()));
DCTCP requires ECN to be enabled:
::
Config::SetDefault ("ns3::TcpSocketBase::EcnMode", StringValue ("ClassicEcn"));
DCTCP depends on a simple queue management algorithm in routers / switches to
mark packets. The current implementation of DCTCP in ns-3 uses RED with a simple
configuration to achieve the behavior of desired queue management algorithm.
To configure RED router for DCTCP:
::
Config::SetDefault ("ns3::RedQueueDisc::UseEcn", BooleanValue (true));
Config::SetDefault ("ns3::RedQueueDisc::QW", DoubleValue (1.0));
The following unit tests have been written to validate the implementation of DCTCP:
* ECT flags should be set for SYN, SYN+ACK, ACK and data packets for DCTCP traffic
* ECT flags should not be set for SYN, SYN+ACK and pure ACK packets, but should be set on data packets for ECN enabled traditional TCP flows
* ECE should be set only when CE flags are received at receiver and even if sender doesnt send CWR, receiver should not send ECE if it doesnt receive packets with CE flags
* Test to validate cwnd increment in DCTCP
* Test to validate cwnd decrement in DCTCP
More information about DCTCP is available in the RFC 8257:
https://tools.ietf.org/html/rfc8257
Support for Explicit Congestion Notification (ECN)
++++++++++++++++++++++++++++++++++++++++++++++++++

View File

@@ -0,0 +1,245 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2017 NITK Surathkal
*
* 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
*
* Author: Shravya K.S. <shravya.ks0@gmail.com>
*
*/
#include "tcp-dctcp.h"
#include "ns3/log.h"
#include "math.h"
#include "ns3/tcp-socket-state.h"
namespace ns3 {
NS_LOG_COMPONENT_DEFINE ("TcpDctcp");
NS_OBJECT_ENSURE_REGISTERED (TcpDctcp);
TypeId TcpDctcp::GetTypeId (void)
{
static TypeId tid = TypeId ("ns3::TcpDctcp")
.SetParent<TcpNewReno> ()
.AddConstructor<TcpDctcp> ()
.SetGroupName ("Internet")
.AddAttribute ("DctcpShiftG",
"Parameter G for updating dctcp_alpha",
DoubleValue (0.0625),
MakeDoubleAccessor (&TcpDctcp::m_g),
MakeDoubleChecker<double> (0))
.AddAttribute ("DctcpAlphaOnInit",
"Initial alpha value",
DoubleValue (1.0),
MakeDoubleAccessor (&TcpDctcp::SetDctcpAlpha),
MakeDoubleChecker<double> (0))
;
return tid;
}
std::string TcpDctcp::GetName () const
{
return "TcpDctcp";
}
TcpDctcp::TcpDctcp ()
: TcpNewReno ()
{
NS_LOG_FUNCTION (this);
m_delayedAckReserved = false;
m_ceState = false;
m_ackedBytesEcn = 0;
m_ackedBytesTotal = 0;
m_priorRcvNxtFlag = false;
m_nextSeqFlag = false;
}
TcpDctcp::TcpDctcp (const TcpDctcp& sock)
: TcpNewReno (sock)
{
NS_LOG_FUNCTION (this);
m_delayedAckReserved = (sock.m_delayedAckReserved);
m_ceState = (sock.m_ceState);
}
TcpDctcp::~TcpDctcp (void)
{
NS_LOG_FUNCTION (this);
}
Ptr<TcpCongestionOps> TcpDctcp::Fork (void)
{
NS_LOG_FUNCTION (this);
return CopyObject<TcpDctcp> (this);
}
void
TcpDctcp::ReduceCwnd (Ptr<TcpSocketState> tcb)
{
NS_LOG_FUNCTION (this << tcb);
uint32_t val = (int)((1 - m_alpha / 2.0) * tcb->m_cWnd);
tcb->m_cWnd = std::max (val, 2 * tcb->m_segmentSize);
}
void
TcpDctcp::PktsAcked (Ptr<TcpSocketState> tcb, uint32_t segmentsAcked, const Time &rtt)
{
NS_LOG_FUNCTION (this << tcb << segmentsAcked << rtt);
m_ackedBytesTotal += segmentsAcked * tcb->m_segmentSize;
if (tcb->m_ecnState == TcpSocketState::ECN_ECE_RCVD)
{
m_ackedBytesEcn += segmentsAcked * tcb->m_segmentSize;
}
if (m_nextSeqFlag == false)
{
m_nextSeq = tcb->m_nextTxSequence;
m_nextSeqFlag = true;
}
if (tcb->m_lastAckedSeq >= m_nextSeq)
{
double bytesEcn;
if (m_ackedBytesTotal > 0)
{
bytesEcn = (double) m_ackedBytesEcn / m_ackedBytesTotal;
}
else
{
bytesEcn = 0.0;
}
m_alpha = (1.0 - m_g) * m_alpha + m_g * bytesEcn;
Reset (tcb);
}
}
void
TcpDctcp::SetDctcpAlpha (double alpha)
{
NS_LOG_FUNCTION (this << alpha);
m_alpha = alpha;
}
void
TcpDctcp::Reset (Ptr<TcpSocketState> tcb)
{
NS_LOG_FUNCTION (this << tcb);
m_nextSeq = tcb->m_nextTxSequence;
m_ackedBytesEcn = 0;
m_ackedBytesTotal = 0;
}
void
TcpDctcp::CeState0to1 (Ptr<TcpSocketState> tcb)
{
NS_LOG_FUNCTION (this << tcb);
if (!m_ceState && m_delayedAckReserved && m_priorRcvNxtFlag)
{
SequenceNumber32 tmpRcvNxt;
/* Save current NextRxSequence. */
tmpRcvNxt = tcb->m_rxBuffer->NextRxSequence ();
/* Generate previous ACK without ECE */
tcb->m_rxBuffer->SetNextRxSequence (m_priorRcvNxt);
tcb->m_sendEmptyPacketCallback (TcpHeader::ACK);
/* Recover current RcvNxt. */
tcb->m_rxBuffer->SetNextRxSequence (tmpRcvNxt);
}
if (m_priorRcvNxtFlag == false)
{
m_priorRcvNxtFlag = true;
}
m_priorRcvNxt = tcb->m_rxBuffer->NextRxSequence ();
m_ceState = true;
tcb->m_ecnState = TcpSocketState::ECN_CE_RCVD;
}
void
TcpDctcp::CeState1to0 (Ptr<TcpSocketState> tcb)
{
NS_LOG_FUNCTION (this << tcb);
if (m_ceState && m_delayedAckReserved && m_priorRcvNxtFlag)
{
SequenceNumber32 tmpRcvNxt;
/* Save current NextRxSequence. */
tmpRcvNxt = tcb->m_rxBuffer->NextRxSequence ();
/* Generate previous ACK with ECE */
tcb->m_rxBuffer->SetNextRxSequence (m_priorRcvNxt);
tcb->m_sendEmptyPacketCallback (TcpHeader::ACK | TcpHeader::ECE);
/* Recover current RcvNxt. */
tcb->m_rxBuffer->SetNextRxSequence (tmpRcvNxt);
}
if (m_priorRcvNxtFlag == false)
{
m_priorRcvNxtFlag = true;
}
m_priorRcvNxt = tcb->m_rxBuffer->NextRxSequence ();
m_ceState = false;
tcb->m_ecnState = TcpSocketState::ECN_IDLE;
}
void
TcpDctcp::UpdateAckReserved (Ptr<TcpSocketState> tcb,
const TcpSocketState::TcpCAEvent_t event)
{
NS_LOG_FUNCTION (this << tcb << event);
switch (event)
{
case TcpSocketState::CA_EVENT_DELAYED_ACK:
if (!m_delayedAckReserved)
{
m_delayedAckReserved = true;
}
break;
case TcpSocketState::CA_EVENT_NON_DELAYED_ACK:
if (m_delayedAckReserved)
{
m_delayedAckReserved = false;
}
break;
default:
/* Don't care for the rest. */
break;
}
}
void
TcpDctcp::CwndEvent (Ptr<TcpSocketState> tcb,
const TcpSocketState::TcpCAEvent_t event)
{
NS_LOG_FUNCTION (this << tcb << event);
switch (event)
{
case TcpSocketState::CA_EVENT_ECN_IS_CE:
CeState0to1 (tcb);
break;
case TcpSocketState::CA_EVENT_ECN_NO_CE:
CeState1to0 (tcb);
break;
case TcpSocketState::CA_EVENT_DELAYED_ACK:
case TcpSocketState::CA_EVENT_NON_DELAYED_ACK:
UpdateAckReserved (tcb, event);
break;
default:
/* Don't care for the rest. */
break;
}
}
}

View File

@@ -0,0 +1,148 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2017 NITK Surathkal
*
* 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
*
* Author: Shravya K.S. <shravya.ks0@gmail.com>
*
*/
#ifndef TCP_DCTCP_H
#define TCP_DCTCP_H
#include "ns3/tcp-congestion-ops.h"
namespace ns3 {
/**
* \ingroup congestionOps
*
* \brief An implementation of DCTCP. This model implements all the functionalities mentioned
* in the DCTCP SIGCOMM paper except dynamic buffer allocation in switches
*/
class TcpDctcp : public TcpNewReno
{
public:
/**
* \brief Get the type ID.
* \return the object TypeId
*/
static TypeId GetTypeId (void);
/**
* Create an unbound tcp socket.
*/
TcpDctcp ();
/**
* \brief Copy constructor
* \param sock the object to copy
*/
TcpDctcp (const TcpDctcp& sock);
/**
* \brief Destructor
*/
virtual ~TcpDctcp (void);
/**
* \brief Get the name of the TCP flavour
*
* \return The name of the TCP
*/
virtual std::string GetName () const;
virtual Ptr<TcpCongestionOps> Fork ();
/**
* \brief Reduce congestion window based on DCTCP algorithm
*
* \param tcb internal congestion state
*/
virtual void ReduceCwnd (Ptr<TcpSocketState> tcb);
/**
* \brief Get information from the acked packet
*
* \param tcb internal congestion state
* \param segmentsAcked count of segments ACKed
* \param rtt The estimated rtt
*/
virtual void PktsAcked (Ptr<TcpSocketState> tcb, uint32_t segmentsAcked,
const Time &rtt);
/**
* \brief Trigger events/calculations on occurrence of congestion window event
*
* \param tcb internal state
* \param event congestion window event which triggered this function
*/
virtual void CwndEvent (Ptr<TcpSocketState> tcb,
const TcpSocketState::TcpCAEvent_t event);
private:
/**
* \brief Changes state of m_ceState to true
*
* \param tcb internal congestion state
*/
void CeState0to1 (Ptr<TcpSocketState> tcb);
/**
* \brief Changes state of m_ceState to false
*
* \param tcb internal congestion state
*/
void CeState1to0 (Ptr<TcpSocketState> tcb);
/**
* \brief Updates the value of m_delayedAckReserved
*
* \param tcb internal congestion state
* \param event the congestion window event
*/
void UpdateAckReserved (Ptr<TcpSocketState> tcb,
const TcpSocketState::TcpCAEvent_t event);
/**
* \brief Resets the value of m_ackedBytesEcn, m_ackedBytesTotal and m_nextSeq
*
* \param tcb internal congestion state
*/
void Reset (Ptr<TcpSocketState> tcb);
/**
* \brief Sets the value of m_alpha
*
* \param alpha DCTCP alpha parameter
*/
void SetDctcpAlpha (double alpha);
uint32_t m_ackedBytesEcn; //!< Number of acked bytes which are marked
uint32_t m_ackedBytesTotal; //!< Total number of acked bytes
SequenceNumber32 m_priorRcvNxt; //!< Sequence number of the first missing byte in data
bool m_priorRcvNxtFlag; //!< Variable used in setting the value of m_priorRcvNxt for first time
double m_alpha; //!< Parameter used to estimate the amount of network congestion
SequenceNumber32 m_nextSeq; //!< TCP sequence number threshold for beginning a new observation window
bool m_nextSeqFlag; //!< Variable used in setting the value of m_nextSeq for first time
bool m_ceState; //!< DCTCP Congestion Experienced state
bool m_delayedAckReserved; //!< Delayed Ack state
double m_g; //!< Estimation gain
};
} // namespace ns3
#endif /* TCP_DCTCP_H */

View File

@@ -2088,7 +2088,7 @@ TcpSocketBase::ProcessSynSent (Ptr<Packet> packet, const TcpHeader& tcpHeader)
m_state = SYN_RCVD;
m_synCount = m_synRetries;
m_tcb->m_rxBuffer->SetNextRxSequence (tcpHeader.GetSequenceNumber () + SequenceNumber32 (1));
/* Check if we recieved an ECN SYN packet. Change the ECN state of receiver to ECN_IDLE if the traffic is ECN capable and
/* Check if we received an ECN SYN packet. Change the ECN state of receiver to ECN_IDLE if the traffic is ECN capable and
* sender has sent ECN SYN packet
*/
if (m_ecnMode == EcnMode_t::ClassicEcn && (tcpflags & (TcpHeader::CWR | TcpHeader::ECE)) == (TcpHeader::CWR | TcpHeader::ECE))
@@ -2806,10 +2806,17 @@ TcpSocketBase::AddSocketTags (const Ptr<Packet> &p) const
if (GetIpTos ())
{
SocketIpTosTag ipTosTag;
if (m_tcb->m_ecnState != TcpSocketState::ECN_DISABLED && CheckEcnEct0 (GetIpTos ()))
if (m_tcb->m_ecnState != TcpSocketState::ECN_DISABLED && !CheckNoEcn (GetIpTos ()))
{
// Set ECT(0) if ECN is enabled with the last received ipTos
ipTosTag.SetTos (MarkEcnEct0 (GetIpTos ()));
// Classic traffic have ECT(0) flags whereas L4S have ECT(1) flags set with the last received ipTos
if (m_congestionControl->GetName () == "TcpDctcp")
{
ipTosTag.SetTos (MarkEcnEct1 (GetIpTos ()));
}
else
{
ipTosTag.SetTos (MarkEcnEct0 (GetIpTos ()));
}
}
else
{
@@ -2822,9 +2829,22 @@ TcpSocketBase::AddSocketTags (const Ptr<Packet> &p) const
{
if (m_tcb->m_ecnState != TcpSocketState::ECN_DISABLED && p->GetSize () > 0)
{
// Set ECT(0) if ECN is enabled and ipTos is 0
// Classic traffic have ECT0 flags whereas L4S have ECT1 flags set
SocketIpTosTag ipTosTag;
ipTosTag.SetTos (MarkEcnEct0 (GetIpTos ()));
if (m_congestionControl->GetName () == "TcpDctcp")
{
ipTosTag.SetTos (MarkEcnEct1 (GetIpTos ()));
}
else
{
ipTosTag.SetTos (MarkEcnEct0 (GetIpTos ()));
}
p->AddPacketTag (ipTosTag);
}
else if (m_congestionControl->GetName () == "TcpDctcp")
{
SocketIpTosTag ipTosTag;
ipTosTag.SetTos (MarkEcnEct1 (GetIpTos ()));
p->AddPacketTag (ipTosTag);
}
}
@@ -2832,10 +2852,17 @@ TcpSocketBase::AddSocketTags (const Ptr<Packet> &p) const
if (IsManualIpv6Tclass ())
{
SocketIpv6TclassTag ipTclassTag;
if (m_tcb->m_ecnState != TcpSocketState::ECN_DISABLED && CheckEcnEct0 (GetIpv6Tclass ()))
if (m_tcb->m_ecnState != TcpSocketState::ECN_DISABLED && !CheckNoEcn (GetIpv6Tclass ()))
{
// Set ECT(0) if ECN is enabled with the last received ipTos
ipTclassTag.SetTclass (MarkEcnEct0 (GetIpv6Tclass ()));
//Classic traffic have ECT0 flags whereas L4S have ECT1 flags set
if (m_congestionControl->GetName () == "TcpDctcp")
{
ipTclassTag.SetTclass (MarkEcnEct1 (GetIpv6Tclass ()));
}
else
{
ipTclassTag.SetTclass (MarkEcnEct0 (GetIpv6Tclass ()));
}
}
else
{
@@ -2848,7 +2875,20 @@ TcpSocketBase::AddSocketTags (const Ptr<Packet> &p) const
{
if (m_tcb->m_ecnState != TcpSocketState::ECN_DISABLED && p->GetSize () > 0)
{
// Set ECT(0) if ECN is enabled and ipTos is 0
SocketIpv6TclassTag ipTclassTag;
// Classic traffic have ECT0 flags whereas L4S have ECT1 flags set
if (m_congestionControl->GetName () == "TcpDctcp")
{
ipTclassTag.SetTclass (MarkEcnEct1 (GetIpv6Tclass ()));
}
else
{
ipTclassTag.SetTclass (MarkEcnEct0 (GetIpv6Tclass ()));
}
p->AddPacketTag (ipTclassTag);
}
else if (m_congestionControl->GetName () == "TcpDctcp")
{
SocketIpv6TclassTag ipTclassTag;
ipTclassTag.SetTclass (MarkEcnEct0 (GetIpv6Tclass ()));
p->AddPacketTag (ipTclassTag);

View File

@@ -0,0 +1,690 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2017 NITK Surathkal
*
* 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
*
* Author: Shravya K.S. <shravya.ks0@gmail.com>
*
*/
#include "ns3/ipv4.h"
#include "ns3/ipv6.h"
#include "../model/ipv4-end-point.h"
#include "../model/ipv6-end-point.h"
#include "tcp-general-test.h"
#include "ns3/node.h"
#include "ns3/log.h"
#include "tcp-error-model.h"
#include "ns3/tcp-l4-protocol.h"
#include "ns3/tcp-dctcp.h"
#include "ns3/tcp-tx-buffer.h"
#include "ns3/config.h"
using namespace ns3;
NS_LOG_COMPONENT_DEFINE ("TcpDctcpTestSuite");
/**
* \ingroup internet-test
* \ingroup tests
*
* \brief Validates the setting of ECT and ECE codepoints for DCTCP enabled traffic
*/
class TcpDctcpCodePointsTest : public TcpGeneralTest
{
public:
/**
* \brief Constructor
*
* \param testCase Test case number
* \param desc Description about the test
*/
TcpDctcpCodePointsTest (uint8_t testCase, const std::string &desc);
protected:
virtual void Tx (const Ptr<const Packet> p, const TcpHeader&h, SocketWho who);
virtual void Rx (const Ptr<const Packet> p, const TcpHeader&h, SocketWho who);
virtual Ptr<TcpSocketMsgBase> CreateSenderSocket (Ptr<Node> node);
virtual Ptr<TcpSocketMsgBase> CreateReceiverSocket (Ptr<Node> node);
void ConfigureProperties ();
private:
uint32_t m_senderSent;
uint32_t m_receiverSent;
uint32_t m_senderReceived;
uint8_t m_testCase;
};
TcpDctcpCodePointsTest::TcpDctcpCodePointsTest (uint8_t testCase, const std::string &desc)
: TcpGeneralTest (desc),
m_senderSent (0),
m_receiverSent (0),
m_senderReceived (0),
m_testCase (testCase)
{
}
void
TcpDctcpCodePointsTest::Tx (const Ptr<const Packet> p, const TcpHeader &h, SocketWho who)
{
if (who == SENDER && (m_testCase == 1 || m_testCase == 2))
{
m_senderSent++;
SocketIpTosTag ipTosTag;
p->PeekPacketTag (ipTosTag);
if (m_testCase == 1)
{
if (m_senderSent == 1)
{
NS_TEST_ASSERT_MSG_EQ (unsigned (ipTosTag.GetTos ()), 0x1, "IP TOS should have ECT1 for SYN packet for DCTCP traffic");
}
if (m_senderSent == 3)
{
NS_TEST_ASSERT_MSG_EQ (unsigned (ipTosTag.GetTos ()), 0x1, "IP TOS should have ECT1 for data packets for DCTCP traffic");
}
}
else
{
if (m_senderSent == 1)
{
NS_TEST_ASSERT_MSG_NE (unsigned (ipTosTag.GetTos ()), 0x1, "IP TOS should not have ECT1 for SYN packet for DCTCP traffic");
}
if (m_senderSent == 3)
{
NS_TEST_ASSERT_MSG_EQ (unsigned (ipTosTag.GetTos ()), 0x2, "IP TOS should have ECT0 for data packets for non-DCTCP but ECN enabled traffic");
}
}
}
else if (who == RECEIVER && (m_testCase == 1 || m_testCase == 2))
{
m_receiverSent++;
SocketIpTosTag ipTosTag;
p->PeekPacketTag (ipTosTag);
if (m_testCase == 1)
{
if (m_receiverSent == 1)
{
NS_TEST_ASSERT_MSG_EQ (unsigned (ipTosTag.GetTos ()), 0x1, "IP TOS should have ECT1 for SYN+ACK packet for DCTCP traffic");
}
if (m_receiverSent == 2)
{
NS_TEST_ASSERT_MSG_EQ (unsigned (ipTosTag.GetTos ()), 0x1, "IP TOS should have ECT1 for pure ACK packets for DCTCP traffic");
}
}
else
{
if (m_receiverSent == 1)
{
NS_TEST_ASSERT_MSG_NE (unsigned (ipTosTag.GetTos ()), 0x1, "IP TOS should not have ECT0 for SYN+ACK packet for non-DCTCP traffic");
NS_TEST_ASSERT_MSG_NE (unsigned (ipTosTag.GetTos ()), 0x2, "IP TOS should not have ECT1 for SYN+ACK packet for non-DCTCP traffic");
}
if (m_receiverSent == 2)
{
NS_TEST_ASSERT_MSG_NE (unsigned (ipTosTag.GetTos ()), 0x1, "IP TOS should not have ECT1 for pure ACK packets for non-DCTCP traffic but ECN enabled traffic");
}
}
}
}
void
TcpDctcpCodePointsTest::Rx (const Ptr<const Packet> p, const TcpHeader &h, SocketWho who)
{
if (who == SENDER && m_testCase == 3)
{
m_senderReceived++;
if (m_senderReceived == 2 && m_testCase == 3)
{
NS_TEST_ASSERT_MSG_NE (((h.GetFlags ()) & TcpHeader::ECE), 0, "The flag ECE should be set in TCP header of the packet sent by the receiver when it receives a packet with CE bit set in IP header");
}
if (m_senderReceived > 2 && m_testCase == 3)
{
NS_TEST_ASSERT_MSG_EQ (((h.GetFlags ()) & TcpHeader::ECE), 0, "The flag ECE should be not be set in TCP header of the packet sent by the receiver if it receives a packet without CE bit set in IP header inspite of Sender not sending CWR flags to it");
}
}
}
void
TcpDctcpCodePointsTest::ConfigureProperties ()
{
TcpGeneralTest::ConfigureProperties ();
SetEcn (SENDER, TcpSocketBase::ClassicEcn);
SetEcn (RECEIVER, TcpSocketBase::ClassicEcn);
}
/**
* \ingroup internet-test
* \ingroup tests
*
* \brief A TCP socket which sends a data packet with CE flags set for test 3.
*
* The SendDataPacket function of this class sends data packet numbered 1 with CE flags set and also doesn't set
* CWR flags on receipt of ECE flags for test 3. This is done to verify that DCTCP receiver sends ECE only if it
* receives CE inspite of sender not sending CWR flags for ECE
*
*/
class TcpDctcpCongestedRouter : public TcpSocketMsgBase
{
public:
/**
* \brief Get the type ID.
* \return the object TypeId
*/
static TypeId GetTypeId (void);
uint32_t m_dataPacketSent;
uint8_t m_testCase;
TcpDctcpCongestedRouter ()
: TcpSocketMsgBase ()
{
m_dataPacketSent = 0;
}
/**
* \brief Constructor.
* \param other The object to copy from.
*/
TcpDctcpCongestedRouter (const TcpDctcpCongestedRouter &other)
: TcpSocketMsgBase (other)
{
}
void SetTestCase (uint8_t testCase);
protected:
virtual uint32_t SendDataPacket (SequenceNumber32 seq, uint32_t maxSize, bool withAck);
virtual void ReTxTimeout ();
Ptr<TcpSocketBase> Fork (void);
};
NS_OBJECT_ENSURE_REGISTERED (TcpDctcpCongestedRouter);
TypeId
TcpDctcpCongestedRouter::GetTypeId (void)
{
static TypeId tid = TypeId ("ns3::TcpDctcpCongestedRouter")
.SetParent<TcpSocketMsgBase> ()
.SetGroupName ("Internet")
.AddConstructor<TcpDctcpCongestedRouter> ()
;
return tid;
}
void
TcpDctcpCongestedRouter::ReTxTimeout ()
{
TcpSocketBase::ReTxTimeout ();
}
void
TcpDctcpCongestedRouter::SetTestCase (uint8_t testCase)
{
m_testCase = testCase;
}
uint32_t
TcpDctcpCongestedRouter::SendDataPacket (SequenceNumber32 seq, uint32_t maxSize, bool withAck)
{
NS_LOG_FUNCTION (this << seq << maxSize << withAck);
m_dataPacketSent++;
bool isRetransmission = false;
if (seq != m_tcb->m_highTxMark)
{
isRetransmission = true;
}
Ptr<Packet> p = m_txBuffer->CopyFromSequence (maxSize, seq)->GetPacketCopy ();
uint32_t sz = p->GetSize (); // Size of packet
uint8_t flags = withAck ? TcpHeader::ACK : 0;
uint32_t remainingData = m_txBuffer->SizeFromSequence (seq + SequenceNumber32 (sz));
if (withAck)
{
m_delAckEvent.Cancel ();
m_delAckCount = 0;
}
// For test 3, we don't send CWR flags on receipt of ECE to check if Receiver sends ECE only when there is CE flags
if (m_tcb->m_ecnState == TcpSocketState::ECN_ECE_RCVD && m_ecnEchoSeq.Get () > m_ecnCWRSeq.Get () && !isRetransmission && m_testCase != 3)
{
NS_LOG_INFO ("Backoff mechanism by reducing CWND by half because we've received ECN Echo");
m_congestionControl->ReduceCwnd (m_tcb);
m_tcb->m_ssThresh = m_tcb->m_cWnd;
flags |= TcpHeader::CWR;
m_ecnCWRSeq = seq;
m_tcb->m_ecnState = TcpSocketState::ECN_CWR_SENT;
NS_LOG_DEBUG (TcpSocketState::EcnStateName[m_tcb->m_ecnState] << " -> ECN_CWR_SENT");
NS_LOG_INFO ("CWR flags set");
NS_LOG_DEBUG (TcpSocketState::TcpCongStateName[m_tcb->m_congState] << " -> CA_CWR");
if (m_tcb->m_congState == TcpSocketState::CA_OPEN)
{
m_congestionControl->CongestionStateSet (m_tcb, TcpSocketState::CA_CWR);
m_tcb->m_congState = TcpSocketState::CA_CWR;
}
}
/*
* Add tags for each socket option.
* Note that currently the socket adds both IPv4 tag and IPv6 tag
* if both options are set. Once the packet got to layer three, only
* the corresponding tags will be read.
*/
if (GetIpTos ())
{
SocketIpTosTag ipTosTag;
NS_LOG_LOGIC (" ECT bits should not be set on retransmitted packets ");
if (m_testCase == 3 && m_dataPacketSent == 1 && !isRetransmission)
{
ipTosTag.SetTos (GetIpTos () | 0x3);
}
else
{
if (m_tcb->m_ecnState != TcpSocketState::ECN_DISABLED && (GetIpTos () & 0x3) == 0 && !isRetransmission)
{
ipTosTag.SetTos (GetIpTos () | 0x1);
}
else
{
ipTosTag.SetTos (GetIpTos ());
}
}
p->AddPacketTag (ipTosTag);
}
else
{
SocketIpTosTag ipTosTag;
if (m_testCase == 3 && m_dataPacketSent == 1 && !isRetransmission)
{
ipTosTag.SetTos (0x3);
}
else
{
if (m_tcb->m_ecnState != TcpSocketState::ECN_DISABLED && !isRetransmission)
{
ipTosTag.SetTos (0x1);
}
}
p->AddPacketTag (ipTosTag);
}
if (IsManualIpv6Tclass ())
{
SocketIpv6TclassTag ipTclassTag;
if (m_testCase == 3 && m_dataPacketSent == 1 && !isRetransmission )
{
ipTclassTag.SetTclass (GetIpv6Tclass () | 0x3);
}
else
{
if (m_tcb->m_ecnState != TcpSocketState::ECN_DISABLED && (GetIpv6Tclass () & 0x3) == 0 && !isRetransmission)
{
ipTclassTag.SetTclass (GetIpv6Tclass () | 0x1);
}
else
{
ipTclassTag.SetTclass (GetIpv6Tclass ());
}
}
p->AddPacketTag (ipTclassTag);
}
else
{
SocketIpv6TclassTag ipTclassTag;
if (m_testCase == 3 && m_dataPacketSent == 1 && !isRetransmission)
{
ipTclassTag.SetTclass (0x3);
}
else
{
if (m_tcb->m_ecnState != TcpSocketState::ECN_DISABLED && !isRetransmission)
{
ipTclassTag.SetTclass (0x1);
}
}
p->AddPacketTag (ipTclassTag);
}
if (IsManualIpTtl ())
{
SocketIpTtlTag ipTtlTag;
ipTtlTag.SetTtl (GetIpTtl ());
p->AddPacketTag (ipTtlTag);
}
if (IsManualIpv6HopLimit ())
{
SocketIpv6HopLimitTag ipHopLimitTag;
ipHopLimitTag.SetHopLimit (GetIpv6HopLimit ());
p->AddPacketTag (ipHopLimitTag);
}
uint8_t priority = GetPriority ();
if (priority)
{
SocketPriorityTag priorityTag;
priorityTag.SetPriority (priority);
p->ReplacePacketTag (priorityTag);
}
if (m_closeOnEmpty && (remainingData == 0))
{
flags |= TcpHeader::FIN;
if (m_state == ESTABLISHED)
{ // On active close: I am the first one to send FIN
NS_LOG_DEBUG ("ESTABLISHED -> FIN_WAIT_1");
m_state = FIN_WAIT_1;
}
else if (m_state == CLOSE_WAIT)
{ // On passive close: Peer sent me FIN already
NS_LOG_DEBUG ("CLOSE_WAIT -> LAST_ACK");
m_state = LAST_ACK;
}
}
TcpHeader header;
header.SetFlags (flags);
header.SetSequenceNumber (seq);
header.SetAckNumber (m_tcb->m_rxBuffer->NextRxSequence ());
if (m_endPoint)
{
header.SetSourcePort (m_endPoint->GetLocalPort ());
header.SetDestinationPort (m_endPoint->GetPeerPort ());
}
else
{
header.SetSourcePort (m_endPoint6->GetLocalPort ());
header.SetDestinationPort (m_endPoint6->GetPeerPort ());
}
header.SetWindowSize (AdvertisedWindowSize ());
AddOptions (header);
if (m_retxEvent.IsExpired ())
{
// Schedules retransmit timeout. m_rto should be already doubled.
NS_LOG_LOGIC (this << " SendDataPacket Schedule ReTxTimeout at time " <<
Simulator::Now ().GetSeconds () << " to expire at time " <<
(Simulator::Now () + m_rto.Get ()).GetSeconds () );
m_retxEvent = Simulator::Schedule (m_rto, &TcpDctcpCongestedRouter::ReTxTimeout, this);
}
m_txTrace (p, header, this);
if (m_endPoint)
{
m_tcp->SendPacket (p, header, m_endPoint->GetLocalAddress (),
m_endPoint->GetPeerAddress (), m_boundnetdevice);
NS_LOG_DEBUG ("Send segment of size " << sz << " with remaining data " <<
remainingData << " via TcpL4Protocol to " << m_endPoint->GetPeerAddress () <<
". Header " << header);
}
else
{
m_tcp->SendPacket (p, header, m_endPoint6->GetLocalAddress (),
m_endPoint6->GetPeerAddress (), m_boundnetdevice);
NS_LOG_DEBUG ("Send segment of size " << sz << " with remaining data " <<
remainingData << " via TcpL4Protocol to " << m_endPoint6->GetPeerAddress () <<
". Header " << header);
}
UpdateRttHistory (seq, sz, isRetransmission);
// Notify the application of the data being sent unless this is a retransmit
if (seq + sz > m_tcb->m_highTxMark)
{
Simulator::ScheduleNow (&TcpDctcpCongestedRouter::NotifyDataSent, this,
(seq + sz - m_tcb->m_highTxMark.Get ()));
}
// Update highTxMark
m_tcb->m_highTxMark = std::max (seq + sz, m_tcb->m_highTxMark.Get ());
return sz;
}
Ptr<TcpSocketBase>
TcpDctcpCongestedRouter::Fork (void)
{
return CopyObject<TcpDctcpCongestedRouter> (this);
}
Ptr<TcpSocketMsgBase>
TcpDctcpCodePointsTest::CreateSenderSocket (Ptr<Node> node)
{
if (m_testCase == 2)
{
return TcpGeneralTest::CreateSenderSocket (node);
}
else if (m_testCase == 3)
{
Ptr<TcpDctcpCongestedRouter> socket = DynamicCast<TcpDctcpCongestedRouter> (
CreateSocket (node,
TcpDctcpCongestedRouter::GetTypeId (),
TcpDctcp::GetTypeId ()));
socket->SetTestCase (m_testCase);
return socket;
}
else
{
return TcpGeneralTest::CreateSocket (node, TcpSocketMsgBase::GetTypeId (), TcpDctcp::GetTypeId ());
}
}
Ptr<TcpSocketMsgBase>
TcpDctcpCodePointsTest::CreateReceiverSocket (Ptr<Node> node)
{
if (m_testCase == 2)
{
return TcpGeneralTest::CreateReceiverSocket (node);
}
else
{
return TcpGeneralTest::CreateSocket (node, TcpSocketMsgBase::GetTypeId (), TcpDctcp::GetTypeId ());
}
}
/**
* \ingroup internet-test
* \ingroup tests
*
* \brief DCTCP should be same as NewReno during slow start
*/
class TcpDctcpToNewReno : public TestCase
{
public:
/**
* \brief Constructor
*
* \param cWnd congestion window
* \param segmentSize segment size
* \param ssThresh slow start threshold
* \param segmentsAcked segments acked
* \param highTxMark high tx mark
* \param lastAckedSeq last acked seq
* \param rtt RTT
* \param name Name of the test
*/
TcpDctcpToNewReno (uint32_t cWnd, uint32_t segmentSize, uint32_t ssThresh,
uint32_t segmentsAcked, SequenceNumber32 highTxMark,
SequenceNumber32 lastAckedSeq, Time rtt, const std::string &name);
private:
virtual void DoRun (void);
/** \brief Execute the test
*/
void ExecuteTest (void);
uint32_t m_cWnd; //!< cWnd
uint32_t m_segmentSize; //!< segment size
uint32_t m_segmentsAcked; //!< segments acked
uint32_t m_ssThresh; //!< ss thresh
Time m_rtt; //!< rtt
SequenceNumber32 m_highTxMark; //!< high tx mark
SequenceNumber32 m_lastAckedSeq; //!< last acked seq
Ptr<TcpSocketState> m_state; //!< state
};
TcpDctcpToNewReno::TcpDctcpToNewReno (uint32_t cWnd, uint32_t segmentSize, uint32_t ssThresh,
uint32_t segmentsAcked, SequenceNumber32 highTxMark,
SequenceNumber32 lastAckedSeq, Time rtt, const std::string &name)
: TestCase (name),
m_cWnd (cWnd),
m_segmentSize (segmentSize),
m_segmentsAcked (segmentsAcked),
m_ssThresh (ssThresh),
m_rtt (rtt),
m_highTxMark (highTxMark),
m_lastAckedSeq (lastAckedSeq)
{
}
void
TcpDctcpToNewReno::DoRun ()
{
Simulator::Schedule (Seconds (0.0), &TcpDctcpToNewReno::ExecuteTest, this);
Simulator::Run ();
Simulator::Destroy ();
}
void
TcpDctcpToNewReno::ExecuteTest ()
{
m_state = CreateObject <TcpSocketState> ();
m_state->m_cWnd = m_cWnd;
m_state->m_ssThresh = m_ssThresh;
m_state->m_segmentSize = m_segmentSize;
m_state->m_highTxMark = m_highTxMark;
m_state->m_lastAckedSeq = m_lastAckedSeq;
Ptr<TcpSocketState> state = CreateObject <TcpSocketState> ();
state->m_cWnd = m_cWnd;
state->m_ssThresh = m_ssThresh;
state->m_segmentSize = m_segmentSize;
state->m_highTxMark = m_highTxMark;
state->m_lastAckedSeq = m_lastAckedSeq;
Ptr<TcpDctcp> cong = CreateObject <TcpDctcp> ();
cong->IncreaseWindow (m_state, m_segmentsAcked);
Ptr<TcpNewReno> NewRenoCong = CreateObject <TcpNewReno> ();
NewRenoCong->IncreaseWindow (state, m_segmentsAcked);
NS_TEST_ASSERT_MSG_EQ (m_state->m_cWnd.Get (), state->m_cWnd.Get (),
"cWnd has not updated correctly");
}
/**
* \ingroup internet-test
* \ingroup tests
*
* \brief Test to validate cWnd decrement DCTCP
*/
class TcpDctcpDecrementTest : public TestCase
{
public:
/**
* \brief Constructor
*
* \param cWnd congestion window
* \param segmentSize segment size
* \param segmentsAcked segments acked
* \param highTxMark high tx mark
* \param lastAckedSeq last acked seq
* \param rtt RTT
* \param name Name of the test
*/
TcpDctcpDecrementTest (uint32_t cWnd, uint32_t segmentSize, uint32_t segmentsAcked, SequenceNumber32 nextTxSequence,
SequenceNumber32 lastAckedSeq, Time rtt, const std::string &name);
private:
virtual void DoRun (void);
/** \brief Execute the test
*/
void ExecuteTest (void);
uint32_t m_cWnd; //!< cWnd
uint32_t m_segmentSize; //!< segment size
uint32_t m_segmentsAcked; //!< segments acked
Time m_rtt; //!< rtt
SequenceNumber32 m_nextTxSequence; //!< next seq num to be sent
SequenceNumber32 m_lastAckedSeq; //!< last acked seq
Ptr<TcpSocketState> m_state; //!< state
};
TcpDctcpDecrementTest::TcpDctcpDecrementTest (uint32_t cWnd, uint32_t segmentSize, uint32_t segmentsAcked, SequenceNumber32 nextTxSequence,
SequenceNumber32 lastAckedSeq, Time rtt, const std::string &name)
: TestCase (name),
m_cWnd (cWnd),
m_segmentSize (segmentSize),
m_segmentsAcked (segmentsAcked),
m_rtt (rtt),
m_nextTxSequence (nextTxSequence),
m_lastAckedSeq (lastAckedSeq)
{
}
void
TcpDctcpDecrementTest::DoRun ()
{
Config::SetDefault ("ns3::TcpDctcp::DctcpAlphaOnInit", DoubleValue (0));
Simulator::Schedule (Seconds (0.0), &TcpDctcpDecrementTest::ExecuteTest, this);
Simulator::Run ();
Simulator::Destroy ();
}
void
TcpDctcpDecrementTest::ExecuteTest (void)
{
m_state = CreateObject <TcpSocketState> ();
m_state->m_cWnd = m_cWnd;
m_state->m_segmentSize = m_segmentSize;
m_state->m_nextTxSequence = m_nextTxSequence;
m_state->m_lastAckedSeq = m_lastAckedSeq;
Ptr<TcpDctcp> cong = CreateObject <TcpDctcp> ();
m_state->m_ecnState = TcpSocketState::ECN_IDLE;
cong->PktsAcked (m_state, m_segmentsAcked, m_rtt);
cong->ReduceCwnd (m_state);
NS_TEST_ASSERT_MSG_EQ (m_state->m_cWnd.Get (), m_cWnd,
"cWnd has updated correctly");
m_state->m_ecnState = TcpSocketState::ECN_ECE_RCVD;
cong->PktsAcked (m_state, m_segmentsAcked, m_rtt);
cong->ReduceCwnd (m_state);
uint32_t val = (uint32_t)(m_cWnd * (1 - 0.0625 / 2.0));
NS_TEST_ASSERT_MSG_EQ (m_state->m_cWnd.Get (), val,
"cWnd has updated correctly");
}
/**
* \ingroup internet-test
* \ingroup tests
*
* \brief TCP DCTCP TestSuite
*/
class TcpDctcpTestSuite : public TestSuite
{
public:
TcpDctcpTestSuite () : TestSuite ("tcp-dctcp-test", UNIT)
{
AddTestCase (new TcpDctcpToNewReno (2 * 1446, 1446, 4 * 1446, 2, SequenceNumber32 (4753), SequenceNumber32 (3216), MilliSeconds (100), "DCTCP falls to New Reno for slowstart"), TestCase::QUICK);
AddTestCase (new TcpDctcpDecrementTest (4 * 1446, 1446, 2, SequenceNumber32 (3216), SequenceNumber32 (4753), MilliSeconds (100), "DCTCP decrement test"), TestCase::QUICK);
AddTestCase (new TcpDctcpCodePointsTest (1, "ECT Test : Check if ECT is set on Syn, Syn+Ack, Ack and Data packets for DCTCP packets"),
TestCase::QUICK);
AddTestCase (new TcpDctcpCodePointsTest (2, "ECT Test : Check if ECT is not set on Syn, Syn+Ack and Ack but set on Data packets for non-DCTCP but ECN enabled traffic"),TestCase::QUICK);
AddTestCase (new TcpDctcpCodePointsTest (3, "ECE Functionality Test: ECE should only be sent by reciever when it receives CE flags"),
TestCase::QUICK);
}
};
static TcpDctcpTestSuite g_tcpdctcpTest; //!< static var for test initialization

View File

@@ -160,6 +160,7 @@ def build(bld):
'model/tcp-illinois.cc',
'model/tcp-htcp.cc',
'model/tcp-lp.cc',
'model/tcp-dctcp.cc',
'model/tcp-rx-buffer.cc',
'model/tcp-tx-buffer.cc',
'model/tcp-tx-item.cc',
@@ -298,6 +299,7 @@ def build(bld):
'test/tcp-close-test.cc',
'test/icmp-test.cc',
'test/ipv4-deduplication-test.cc',
'test/tcp-dctcp-test.cc',
]
privateheaders = bld(features='ns3privateheader')
privateheaders.module = 'internet'
@@ -402,6 +404,7 @@ def build(bld):
'model/tcp-illinois.h',
'model/tcp-htcp.h',
'model/tcp-lp.h',
'model/tcp-dctcp.h',
'model/tcp-ledbat.h',
'model/tcp-socket-base.h',
'model/tcp-socket-state.h',