diff --git a/src/internet/model/tcp-newreno.cc b/src/internet/model/tcp-congestion-ops.cc similarity index 54% rename from src/internet/model/tcp-newreno.cc rename to src/internet/model/tcp-congestion-ops.cc index 8b63f3b7c..bf7c7b53e 100644 --- a/src/internet/model/tcp-newreno.cc +++ b/src/internet/model/tcp-congestion-ops.cc @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* - * Copyright (c) 2010 Adrian Sai-wah Tam + * Copyright (c) 2015 Natale Patriciello * * 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 @@ -15,93 +15,70 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * - * Author: Adrian Sai-wah Tam */ - -#define NS_LOG_APPEND_CONTEXT \ - if (m_node) { std::clog << Simulator::Now ().GetSeconds () << " [node " << m_node->GetId () << "] "; } - -#include "tcp-newreno.h" +#include "tcp-congestion-ops.h" +#include "tcp-socket-base.h" #include "ns3/log.h" -#include "ns3/trace-source-accessor.h" -#include "ns3/simulator.h" -#include "ns3/abort.h" -#include "ns3/node.h" namespace ns3 { NS_LOG_COMPONENT_DEFINE ("TcpNewReno"); +NS_OBJECT_ENSURE_REGISTERED (TcpCongestionOps); + +TypeId +TcpCongestionOps::GetTypeId (void) +{ + static TypeId tid = TypeId ("ns3::TcpCongestionOps") + .SetParent () + .SetGroupName ("Internet") + ; + return tid; +} + +TcpCongestionOps::TcpCongestionOps () : Object () +{ +} + +TcpCongestionOps::TcpCongestionOps (const TcpCongestionOps &other) : Object (other) +{ +} + +TcpCongestionOps::~TcpCongestionOps () +{ +} + + +// RENO + NS_OBJECT_ENSURE_REGISTERED (TcpNewReno); TypeId TcpNewReno::GetTypeId (void) { static TypeId tid = TypeId ("ns3::TcpNewReno") - .SetParent () + .SetParent () .SetGroupName ("Internet") .AddConstructor () - ; + ; return tid; } -TcpNewReno::TcpNewReno (void) - : TcpSocketBase () +TcpNewReno::TcpNewReno (void) : TcpCongestionOps () { NS_LOG_FUNCTION (this); } TcpNewReno::TcpNewReno (const TcpNewReno& sock) - : TcpSocketBase (sock) + : TcpCongestionOps (sock) { NS_LOG_FUNCTION (this); - NS_LOG_LOGIC ("Invoked the copy constructor"); } TcpNewReno::~TcpNewReno (void) { } -Ptr -TcpNewReno::Fork (void) -{ - return CopyObject (this); -} - -/* New ACK (up to seqnum seq) received. Increase cwnd and call TcpSocketBase::NewAck() */ -void -TcpNewReno::NewAck (const SequenceNumber32& seq) -{ - NS_LOG_FUNCTION (this << seq); - - // No cWnd management while recovering - if (m_ackState == RECOVERY && seq < m_recover) - { - TcpSocketBase::NewAck (seq); - return; - } - - uint32_t segmentsAcked = (seq - m_txBuffer->HeadSequence ()) / m_tcb->m_segmentSize; - - NS_LOG_LOGIC ("TcpNewReno received ACK for seq " << seq << - " cwnd " << m_tcb->m_cWnd << - " ssthresh " << m_tcb->m_ssThresh << - " acked " << segmentsAcked); - - if (m_tcb->m_cWnd <= m_tcb->m_ssThresh) - { - segmentsAcked = SlowStart (segmentsAcked); - } - - if (m_tcb->m_cWnd > m_tcb->m_ssThresh) - { - CongestionAvoidance (segmentsAcked); - } - - // Complete newAck processing - TcpSocketBase::NewAck (seq); -} - /** * \brief Tcp NewReno slow start algorithm * @@ -140,21 +117,19 @@ u32 tcp_slow_start(struct tcp_sock *tp, u32 acked) * than a segment size, but we keep count of how many segments we have ignored, * and return them. * + * \param tcb internal congestion state * \param segmentsAcked count of segments acked * \return the number of segments not considered for increasing the cWnd */ uint32_t -TcpNewReno::SlowStart (uint32_t segmentsAcked) +TcpNewReno::SlowStart (Ptr tcb, uint32_t segmentsAcked) { - NS_LOG_FUNCTION (this << segmentsAcked); + NS_LOG_FUNCTION (this << tcb << segmentsAcked); if (segmentsAcked >= 1) { - m_tcb->m_cWnd = m_tcb->m_cWnd.Get () + m_tcb->m_segmentSize; - - NS_LOG_INFO ("In SlowStart, updated to cwnd " << m_tcb->m_cWnd << - " ssthresh " << m_tcb->m_ssThresh << " MSS " << m_tcb->m_segmentSize); - + tcb->m_cWnd += tcb->m_segmentSize; + NS_LOG_INFO ("In SlowStart, updated to cwnd " << tcb->m_cWnd << " ssthresh " << tcb->m_ssThresh); return segmentsAcked - 1; } @@ -167,27 +142,78 @@ TcpNewReno::SlowStart (uint32_t segmentsAcked) * During congestion avoidance, cwnd is incremented by roughly 1 full-sized * segment per round-trip time (RTT). * + * \param tcb internal congestion state * \param segmentsAcked count of segments acked */ void -TcpNewReno::CongestionAvoidance (uint32_t segmentsAcked) +TcpNewReno::CongestionAvoidance (Ptr tcb, uint32_t segmentsAcked) { - NS_LOG_FUNCTION (this << segmentsAcked); + NS_LOG_FUNCTION (this << tcb << segmentsAcked); if (segmentsAcked > 0) { - double adder = static_cast (m_tcb->m_segmentSize * m_tcb->m_segmentSize) / m_tcb->m_cWnd.Get (); + double adder = static_cast (tcb->m_segmentSize * tcb->m_segmentSize) / tcb->m_cWnd.Get (); adder = std::max (1.0, adder); - m_tcb->m_cWnd += static_cast (adder); - NS_LOG_INFO ("In CongAvoid, updated to cwnd " << m_tcb->m_cWnd << - " ssthresh " << m_tcb->m_ssThresh); + tcb->m_cWnd += static_cast (adder); + NS_LOG_INFO ("In CongAvoid, updated to cwnd " << tcb->m_cWnd << + " ssthresh " << tcb->m_ssThresh); } } -uint32_t -TcpNewReno::GetSsThresh () +/** + * \brief Try to increase the cWnd following the NewReno specification + * + * \see SlowStart + * \see CongestionAvoidance + * + * \param tcb internal congestion state + * \param segmentsAcked count of segments acked + */ +void +TcpNewReno::IncreaseWindow (Ptr tcb, uint32_t segmentsAcked) { - return std::max (2 * m_tcb->m_segmentSize, BytesInFlight () / 2); + NS_LOG_FUNCTION (this << tcb << segmentsAcked); + + if (tcb->m_cWnd < tcb->m_ssThresh) + { + segmentsAcked = SlowStart (tcb, segmentsAcked); + } + + if (tcb->m_cWnd >= tcb->m_ssThresh) + { + CongestionAvoidance (tcb, segmentsAcked); + } + + /* At this point, we could have segmentsAcked != 0. This because RFC says + * that in slow start, we should increase cWnd by min (N, SMSS); if in + * slow start we receive a cumulative ACK, it counts only for 1 SMSS of + * increase, wasting the others. + * + * // Uncorrect assert, I am sorry + * NS_ASSERT (segmentsAcked == 0); + */ +} + +std::string +TcpNewReno::GetName () const +{ + return "TcpNewReno"; +} + +uint32_t +TcpNewReno::GetSsThresh (Ptr state, + uint32_t bytesInFlight) +{ + NS_LOG_FUNCTION (this << state << bytesInFlight); + + return std::max (2 * state->m_segmentSize, bytesInFlight / 2); +} + +Ptr +TcpNewReno::Fork () +{ + return CopyObject (this); } } // namespace ns3 + diff --git a/src/internet/model/tcp-congestion-ops.h b/src/internet/model/tcp-congestion-ops.h new file mode 100644 index 000000000..3b46e50e1 --- /dev/null +++ b/src/internet/model/tcp-congestion-ops.h @@ -0,0 +1,168 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2015 Natale Patriciello + * + * 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 + * + */ +#ifndef TCPCONGESTIONOPS_H +#define TCPCONGESTIONOPS_H + +#include "ns3/object.h" +#include "ns3/timer.h" + +namespace ns3 { + +class TcpSocketState; +class TcpSocketBase; + +/** + * \brief Congestion control abstract class + * + * The design is inspired on what Linux v4.0 does (but it has been + * in place since years). The congestion control is splitted from the main + * socket code, and it is a pluggable component. An interface has been defined; + * variables are maintained in the TcpSocketState class, while subclasses of + * TcpCongestionOps operate over an instance of that class. + * + * Only three methods has been utilized right now; however, Linux has many others, + * which can be added later in ns-3. + * + * \see IncreaseWindow + * \see PktsAcked + */ +class TcpCongestionOps : public Object +{ +public: + /** + * \brief Get the type ID. + * \return the object TypeId + */ + static TypeId GetTypeId (void); + + TcpCongestionOps (); + TcpCongestionOps (const TcpCongestionOps &other); + + virtual ~TcpCongestionOps (); + + /** + * \brief Get the name of the congestion control algorithm + * + * \return A string identifying the name + */ + virtual std::string GetName () const = 0; + + /** + * \brief Get the slow start threshold after a loss event + * + * Is guaranteed that the congestion control state (TcpAckState_t) is + * changed BEFORE the invocation of this method. + * The implementator should return the slow start threshold (and not change + * it directly) because, in the future, the TCP implementation may require to + * instantly recover from a loss event (e.g. when there is a network with an high + * reordering factor). + * + * \param tcb internal congestion state + * \param bytesInFlight total bytes in flight + * \return Slow start threshold + */ + virtual uint32_t GetSsThresh (Ptr tcb, + uint32_t bytesInFlight) = 0; + + /** + * \brief Congestion avoidance algorithm implementation + * + * Mimic the function cong_avoid in Linux. New segments have been ACKed, + * and the congestion control duty is to set + * + * The function is allowed to change directly cWnd and/or ssThresh. + * + * \param tcb internal congestion state + * \param segmentsAcked count of segments acked + */ + virtual void IncreaseWindow (Ptr tcb, uint32_t segmentsAcked) = 0; + + /** + * \brief Timing information on received ACK + * + * The function is called every time an ACK is received (only one time + * also for cumulative ACKs) and contains timing information. It is + * optional (congestion controls can not implement it) and the default + * implementation does nothing. + * + * \param tcb internal congestion state + * \param segmentsAcked count of segments acked + * \param rtt last rtt + */ + virtual void PktsAcked (Ptr tcb, uint32_t segmentsAcked, + const Time& rtt) { } + + // Present in Linux but not in ns-3 yet: + /* call before changing ca_state (optional) */ + // void (*set_state)(struct sock *sk, u8 new_state); + /* call when cwnd event occurs (optional) */ + // void (*cwnd_event)(struct sock *sk, enum tcp_ca_event ev); + /* call when ack arrives (optional) */ + // void (*in_ack_event)(struct sock *sk, u32 flags); + /* new value of cwnd after loss (optional) */ + // u32 (*undo_cwnd)(struct sock *sk); + /* hook for packet ack accounting (optional) */ + // void (*pkts_acked)(struct sock *sk, u32 num_acked, s32 rtt_us); + + /** + * \brief Copy the congestion control algorithm across socket + * + * \return a pointer of the copied object + */ + virtual Ptr Fork () = 0; +}; + +/** + * \brief The NewReno implementation + * + * New Reno introduces partial ACKs inside the well-established Reno algorithm. + * This and other modifications are described in RFC 6582. + * + * \see IncreaseWindow + */ +class TcpNewReno : public TcpCongestionOps +{ +public: + /** + * \brief Get the type ID. + * \return the object TypeId + */ + static TypeId GetTypeId (void); + + TcpNewReno (); + TcpNewReno (const TcpNewReno& sock); + + ~TcpNewReno (); + + std::string GetName () const; + + virtual void IncreaseWindow (Ptr tcb, uint32_t segmentsAcked); + virtual uint32_t GetSsThresh (Ptr tcb, + uint32_t bytesInFlight); + + virtual Ptr Fork (); + +protected: + virtual uint32_t SlowStart (Ptr tcb, uint32_t segmentsAcked); + virtual void CongestionAvoidance (Ptr tcb, uint32_t segmentsAcked); +}; + +} // namespace ns3 + +#endif // TCPCONGESTIONOPS_H diff --git a/src/internet/model/tcp-l4-protocol.cc b/src/internet/model/tcp-l4-protocol.cc index 56340e6a3..cf37bf178 100644 --- a/src/internet/model/tcp-l4-protocol.cc +++ b/src/internet/model/tcp-l4-protocol.cc @@ -40,7 +40,7 @@ #include "ipv6-l3-protocol.h" #include "ipv6-routing-protocol.h" #include "tcp-socket-factory-impl.h" -#include "tcp-newreno.h" +#include "tcp-socket-base.h" #include "rtt-estimator.h" #include @@ -77,7 +77,7 @@ TcpL4Protocol::GetTypeId (void) .AddAttribute ("SocketType", "Socket type of TCP objects.", TypeIdValue (TcpNewReno::GetTypeId ()), - MakeTypeIdAccessor (&TcpL4Protocol::m_socketTypeId), + MakeTypeIdAccessor (&TcpL4Protocol::m_congestionTypeId), MakeTypeIdChecker ()) .AddAttribute ("SocketList", "The list of sockets associated to this protocol.", ObjectVectorValue (), @@ -174,18 +174,23 @@ TcpL4Protocol::DoDispose (void) } Ptr -TcpL4Protocol::CreateSocket (TypeId socketTypeId) +TcpL4Protocol::CreateSocket (TypeId congestionTypeId) { - NS_LOG_FUNCTION (this << socketTypeId); + NS_LOG_FUNCTION (this << congestionTypeId.GetName ()); ObjectFactory rttFactory; - ObjectFactory socketFactory; + ObjectFactory congestionAlgorithmFactory; rttFactory.SetTypeId (m_rttTypeId); - socketFactory.SetTypeId (socketTypeId); + congestionAlgorithmFactory.SetTypeId (congestionTypeId); + Ptr rtt = rttFactory.Create (); - Ptr socket = socketFactory.Create (); + Ptr socket = CreateObject (); + Ptr algo = congestionAlgorithmFactory.Create (); + socket->SetNode (m_node); socket->SetTcp (this); socket->SetRtt (rtt); + socket->SetCongestionControlAlgorithm (algo); + m_sockets.push_back (socket); return socket; } @@ -193,7 +198,7 @@ TcpL4Protocol::CreateSocket (TypeId socketTypeId) Ptr TcpL4Protocol::CreateSocket (void) { - return CreateSocket (m_socketTypeId); + return CreateSocket (m_congestionTypeId); } Ipv4EndPoint * diff --git a/src/internet/model/tcp-l4-protocol.h b/src/internet/model/tcp-l4-protocol.h index e66e8db96..cf40cca4e 100644 --- a/src/internet/model/tcp-l4-protocol.h +++ b/src/internet/model/tcp-l4-protocol.h @@ -98,7 +98,7 @@ public: * * \param socketTypeId the socket TypeId */ - Ptr CreateSocket (TypeId socketTypeId); + Ptr CreateSocket (TypeId congestionTypeId); /** * \brief Allocate an IPv4 Endpoint @@ -282,7 +282,7 @@ private: Ipv4EndPointDemux *m_endPoints; //!< A list of IPv4 end points. Ipv6EndPointDemux *m_endPoints6; //!< A list of IPv6 end points. TypeId m_rttTypeId; //!< The RTT Estimator TypeId - TypeId m_socketTypeId; //!< The socket TypeId + TypeId m_congestionTypeId; //!< The socket TypeId std::vector > m_sockets; //!< list of sockets IpL4Protocol::DownTargetCallback m_downTarget; //!< Callback to send packets over IPv4 IpL4Protocol::DownTargetCallback6 m_downTarget6; //!< Callback to send packets over IPv6 diff --git a/src/internet/model/tcp-newreno.h b/src/internet/model/tcp-newreno.h deleted file mode 100644 index ea99bf31e..000000000 --- a/src/internet/model/tcp-newreno.h +++ /dev/null @@ -1,67 +0,0 @@ -/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ -/* - * Copyright (c) 2010 Adrian Sai-wah Tam - * - * 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: Adrian Sai-wah Tam - */ - -#ifndef TCP_NEWRENO_H -#define TCP_NEWRENO_H - -#include "tcp-socket-base.h" - -namespace ns3 { - -/** - * \ingroup socket - * \ingroup tcp - * - * \brief An implementation of a stream socket using TCP. - * - * This class contains the NewReno implementation of TCP, as of \RFC{2582}. - */ -class TcpNewReno : public TcpSocketBase -{ -public: - /** - * \brief Get the type ID. - * \return the object TypeId - */ - static TypeId GetTypeId (void); - /** - * Create an unbound tcp socket. - */ - TcpNewReno (void); - /** - * \brief Copy constructor - * \param sock the object to copy - */ - TcpNewReno (const TcpNewReno& sock); - virtual ~TcpNewReno (void); - -protected: - virtual Ptr Fork (void); // Call CopyObject to clone me - virtual void NewAck (SequenceNumber32 const& seq); // Inc cwnd and call NewAck() of parent - virtual uint32_t GetSsThresh (); - -private: - uint32_t SlowStart (uint32_t segmentsAcked); - void CongestionAvoidance (uint32_t segmentsAcked); -}; - -} // namespace ns3 - -#endif /* TCP_NEWRENO_H */ diff --git a/src/internet/model/tcp-socket-base.cc b/src/internet/model/tcp-socket-base.cc index 32ecba18c..f909fe44d 100644 --- a/src/internet/model/tcp-socket-base.cc +++ b/src/internet/model/tcp-socket-base.cc @@ -66,6 +66,7 @@ TcpSocketBase::GetTypeId (void) static TypeId tid = TypeId ("ns3::TcpSocketBase") .SetParent () .SetGroupName ("Internet") + .AddConstructor () // .AddAttribute ("TcpState", "State in TCP state machine", // TypeId::ATTR_GET, // EnumValue (CLOSED), @@ -98,7 +99,8 @@ TcpSocketBase::GetTypeId (void) MakeBooleanChecker ()) .AddAttribute ("MinRto", "Minimum retransmit timeout value", - TimeValue (Seconds (1.0)), // RFC 6298 says min RTO=1 sec, but Linux uses 200ms. See http://www.postel.org/pipermail/end2end-interest/2004-November/004402.html + TimeValue (Seconds (1.0)), // RFC 6298 says min RTO=1 sec, but Linux uses 200ms. + // See http://www.postel.org/pipermail/end2end-interest/2004-November/004402.html MakeTimeAccessor (&TcpSocketBase::SetMinRto, &TcpSocketBase::GetMinRto), MakeTimeChecker ()) @@ -148,7 +150,7 @@ TcpSocketBase::GetTypeId (void) "ns3::TcpStatesTracedValueCallback") .AddTraceSource ("AckState", "TCP ACK machine state", - MakeTraceSourceAccessor (&TcpSocketBase::m_ackState), + MakeTraceSourceAccessor (&TcpSocketBase::m_ackStateTrace), "ns3::TcpAckStatesTracedValueCallback") .AddTraceSource ("RWND", "Remote side's flow control window", @@ -190,24 +192,42 @@ TcpSocketState::GetTypeId (void) "TCP slow start threshold (bytes)", MakeTraceSourceAccessor (&TcpSocketState::m_ssThresh), "ns3::TracedValue::Uint32Callback") + .AddTraceSource ("AckState", + "TCP ACK machine state", + MakeTraceSourceAccessor (&TcpSocketState::m_ackState), + "ns3::TracedValue::TcpAckStatesTracedValueCallback") ; return tid; } TcpSocketState::TcpSocketState (void) - : Object () + : Object (), + m_cWnd (0), + m_ssThresh (0), + m_initialCWnd (0), + m_initialSsThresh (0), + m_segmentSize (0), + m_ackState (OPEN) { } TcpSocketState::TcpSocketState (const TcpSocketState &other) - : m_cWnd (other.m_cWnd), + : Object (other), + m_cWnd (other.m_cWnd), m_ssThresh (other.m_ssThresh), m_initialCWnd (other.m_initialCWnd), m_initialSsThresh (other.m_initialSsThresh), - m_segmentSize (other.m_segmentSize) + m_segmentSize (other.m_segmentSize), + m_ackState (other.m_ackState) { } +const char* const +TcpSocketState::TcpAckStateName[TcpSocketState::LAST_ACKSTATE] = +{ + "OPEN", "DISORDER", "CWR", "RECOVERY", "LOSS" +}; + TcpSocketBase::TcpSocketBase (void) : m_dupAckCount (0), m_delAckCount (0), @@ -236,10 +256,10 @@ TcpSocketBase::TcpSocketBase (void) m_rcvScaleFactor (0), m_timestampEnabled (true), m_timestampToEcho (0), - m_ackState (OPEN), m_retxThresh (3), - m_limitedTx (false) - + m_limitedTx (false), + m_congestionControl (0), + m_isFirstPartialAck (true) { NS_LOG_FUNCTION (this); m_rxBuffer = CreateObject (); @@ -255,6 +275,10 @@ TcpSocketBase::TcpSocketBase (void) ok = m_tcb->TraceConnectWithoutContext ("SlowStartThreshold", MakeCallback (&TcpSocketBase::UpdateSsThresh, this)); NS_ASSERT (ok == true); + + ok = m_tcb->TraceConnectWithoutContext ("AckState", + MakeCallback (&TcpSocketBase::UpdateAckState, this)); + NS_ASSERT (ok == true); } TcpSocketBase::TcpSocketBase (const TcpSocketBase& sock) @@ -292,10 +316,10 @@ TcpSocketBase::TcpSocketBase (const TcpSocketBase& sock) m_rcvScaleFactor (sock.m_rcvScaleFactor), m_timestampEnabled (sock.m_timestampEnabled), m_timestampToEcho (sock.m_timestampToEcho), - m_ackState (sock.m_ackState), m_retxThresh (sock.m_retxThresh), m_limitedTx (sock.m_limitedTx), - m_tcb (sock.m_tcb) + m_tcb (sock.m_tcb), + m_isFirstPartialAck (sock.m_isFirstPartialAck) { NS_LOG_FUNCTION (this); NS_LOG_LOGIC ("Invoked the copy constructor"); @@ -314,6 +338,11 @@ TcpSocketBase::TcpSocketBase (const TcpSocketBase& sock) SetRecvCallback (vPS); m_txBuffer = CopyObject (sock.m_txBuffer); m_rxBuffer = CopyObject (sock.m_rxBuffer); + m_tcb = CopyObject (sock.m_tcb); + if (sock.m_congestionControl) + { + m_congestionControl = sock.m_congestionControl->Fork (); + } bool ok; @@ -324,6 +353,10 @@ TcpSocketBase::TcpSocketBase (const TcpSocketBase& sock) ok = m_tcb->TraceConnectWithoutContext ("SlowStartThreshold", MakeCallback (&TcpSocketBase::UpdateSsThresh, this)); NS_ASSERT (ok == true); + + ok = m_tcb->TraceConnectWithoutContext ("AckState", + MakeCallback (&TcpSocketBase::UpdateAckState, this)); + NS_ASSERT (ok == true); } TcpSocketBase::~TcpSocketBase (void) @@ -650,11 +683,12 @@ TcpSocketBase::Close (void) /// \bugid{426} claims we should send reset in this case. if (m_rxBuffer->Size () != 0) { - NS_LOG_INFO ("Socket " << this << " << unread rx data during close. Sending reset"); + NS_LOG_WARN ("Socket " << this << " << unread rx data during close. Sending reset." << + "This is probably due to a bad sink application; check its code"); SendRST (); return 0; } - + if (m_txBuffer->SizeFromSequence (m_nextTxSequence) > 0) { // App close with pending data must wait until all data transmitted if (m_closeOnEmpty == false) @@ -672,7 +706,7 @@ int TcpSocketBase::ShutdownSend (void) { NS_LOG_FUNCTION (this); - + //this prevents data from being added to the buffer m_shutdownSend = true; m_closeOnEmpty = true; @@ -683,7 +717,7 @@ TcpSocketBase::ShutdownSend (void) if (m_state == ESTABLISHED || m_state == CLOSE_WAIT) { NS_LOG_INFO("Emtpy tx buffer, send fin"); - SendEmptyPacket (TcpHeader::FIN); + SendEmptyPacket (TcpHeader::FIN); if (m_state == ESTABLISHED) { // On active close: I am the first one to send FIN @@ -694,10 +728,10 @@ TcpSocketBase::ShutdownSend (void) { // On passive close: Peer sent me FIN already NS_LOG_DEBUG ("CLOSE_WAIT -> LAST_ACK"); m_state = LAST_ACK; - } + } } } - + return 0; } @@ -736,7 +770,9 @@ TcpSocketBase::Send (Ptr p, uint32_t flags) { // Try to send the data out if (!m_sendPendingDataEvent.IsRunning ()) { - m_sendPendingDataEvent = Simulator::Schedule (TimeStep (1), &TcpSocketBase::SendPendingData, this, m_connected); + m_sendPendingDataEvent = Simulator::Schedule (TimeStep (1), + &TcpSocketBase::SendPendingData, + this, m_connected); } } return p->GetSize (); @@ -982,7 +1018,7 @@ TcpSocketBase::CloseAndNotify (void) NS_LOG_DEBUG (TcpStateName[m_state] << " -> CLOSED"); m_state = CLOSED; - DeallocateEndPoint (); + DeallocateEndPoint (); } @@ -1251,6 +1287,21 @@ TcpSocketBase::ReceivedAck (Ptr packet, const TcpHeader& tcpHeader) NS_LOG_FUNCTION (this << tcpHeader); NS_ASSERT (0 != (tcpHeader.GetFlags () & TcpHeader::ACK)); + NS_ASSERT (m_tcb->m_segmentSize > 0); + + uint32_t bytesAcked = tcpHeader.GetAckNumber () - m_txBuffer->HeadSequence (); + uint32_t segsAcked = bytesAcked / m_tcb->m_segmentSize; + m_bytesAckedNotProcessed += bytesAcked % m_tcb->m_segmentSize; + + if (m_bytesAckedNotProcessed >= m_tcb->m_segmentSize) + { + segsAcked += 1; + m_bytesAckedNotProcessed -= m_tcb->m_segmentSize; + } + + NS_LOG_LOGIC (" Bytes acked: " << bytesAcked << + " Segments acked: " << segsAcked << + " bytes left: " << m_bytesAckedNotProcessed); NS_LOG_DEBUG ("ACK of " << tcpHeader.GetAckNumber () << " SND.UNA=" << m_txBuffer->HeadSequence () << @@ -1263,14 +1314,16 @@ TcpSocketBase::ReceivedAck (Ptr packet, const TcpHeader& tcpHeader) // There is a DupAck ++m_dupAckCount; - if (m_ackState == OPEN) + if (m_tcb->m_ackState == TcpSocketState::OPEN) { // From Open we go Disorder - NS_ASSERT (m_dupAckCount == 1); - m_ackState = DISORDER; + NS_ASSERT_MSG (m_dupAckCount == 1, "From OPEN->DISORDER but with " << + m_dupAckCount << " dup ACKs"); + m_tcb->m_ackState = TcpSocketState::DISORDER; + NS_LOG_DEBUG ("OPEN -> DISORDER"); } - else if (m_ackState == DISORDER) + else if (m_tcb->m_ackState == TcpSocketState::DISORDER) { if (m_dupAckCount < m_retxThresh && m_limitedTx) { @@ -1282,18 +1335,19 @@ TcpSocketBase::ReceivedAck (Ptr packet, const TcpHeader& tcpHeader) else if (m_dupAckCount == m_retxThresh) { // triple duplicate ack triggers fast retransmit (RFC2582 sec.3 bullet #1) + NS_LOG_DEBUG (TcpSocketState::TcpAckStateName[m_tcb->m_ackState] << + " -> RECOVERY"); m_recover = m_highTxMark; - m_ackState = RECOVERY; + m_tcb->m_ackState = TcpSocketState::RECOVERY; - m_tcb->m_ssThresh = GetSsThresh (); + m_tcb->m_ssThresh = m_congestionControl->GetSsThresh (m_tcb, + BytesInFlight ()); m_tcb->m_cWnd = m_tcb->m_ssThresh + m_dupAckCount * m_tcb->m_segmentSize; NS_LOG_INFO (m_dupAckCount << " dupack. Enter fast recovery mode." << "Reset cwnd to " << m_tcb->m_cWnd << ", ssthresh to " << m_tcb->m_ssThresh << " at fast recovery seqnum " << m_recover); DoRetransmit (); - - NS_LOG_DEBUG (TcpAckStateName[m_ackState] << " -> RECOVERY"); } else { @@ -1301,15 +1355,16 @@ TcpSocketBase::ReceivedAck (Ptr packet, const TcpHeader& tcpHeader) "in DISORDER state"); } } - else if (m_ackState == RECOVERY) + else if (m_tcb->m_ackState == TcpSocketState::RECOVERY) { // Increase cwnd for every additional dupack (RFC2582, sec.3 bullet #3) - m_tcb->m_cWnd += m_tcb->m_segmentSize; - NS_LOG_INFO ("Dupack in fast recovery mode. Increase cwnd to " << m_tcb->m_cWnd); - if (!m_sendPendingDataEvent.IsRunning ()) - { - m_sendPendingDataEvent = Simulator::Schedule (TimeStep (1), &TcpSocketBase::SendPendingData, this, m_connected); - } + m_tcb->m_cWnd += m_tcb->m_segmentSize; + NS_LOG_INFO (m_dupAckCount << " Dupack received in fast recovery mode." + "Increase cwnd to " << m_tcb->m_cWnd); + SendPendingData (m_connected); } + + // Artificially call PktsAcked. After all, one segment has been ACKed. + m_congestionControl->PktsAcked (m_tcb, 1, m_lastRtt); } else if (tcpHeader.GetAckNumber () == m_txBuffer->HeadSequence () && tcpHeader.GetAckNumber () == m_nextTxSequence) @@ -1318,42 +1373,138 @@ TcpSocketBase::ReceivedAck (Ptr packet, const TcpHeader& tcpHeader) } else if (tcpHeader.GetAckNumber () > m_txBuffer->HeadSequence ()) { // Case 3: New ACK, reset m_dupAckCount and update m_txBuffer - if (m_ackState == DISORDER) + bool callCongestionControl = true; + + /* The following switch is made because m_dupAckCount can be + * "inflated" through out-of-order segments (e.g. from retransmission, + * while segments have not been lost but are network-reordered). At + * least one segment has been acked; in the luckiest case, an amount + * equals to segsAcked-m_dupAckCount has not been processed. + * + * To be clear: segsAcked will be passed to PktsAcked, and it should take + * in considerations the times that it has been already called, while newSegsAcked + * will be passed to IncreaseCwnd, and it represents the amount of + * segments that are allowed to increase the cWnd value. + */ + uint32_t newSegsAcked = segsAcked; + if (segsAcked > m_dupAckCount) + { + segsAcked -= m_dupAckCount; + } + else + { + segsAcked = 1; + } + + if (m_tcb->m_ackState == TcpSocketState::DISORDER) { // The network reorder packets. Linux changes the counting lost // packet algorithm from FACK to NewReno. We simply go back in Open. - m_ackState = OPEN; + m_tcb->m_ackState = TcpSocketState::OPEN; + m_congestionControl->PktsAcked (m_tcb, segsAcked, m_lastRtt); + m_dupAckCount = 0; + NS_LOG_DEBUG ("DISORDER -> OPEN"); } - else if (m_ackState == RECOVERY) + else if (m_tcb->m_ackState == TcpSocketState::RECOVERY) { if (tcpHeader.GetAckNumber () < m_recover) - { // Partial ACK, partial window deflation (RFC2582 sec.3 bullet #5 paragraph 3) - m_tcb->m_cWnd += m_tcb->m_segmentSize - - (tcpHeader.GetAckNumber () - m_txBuffer->HeadSequence ()); - NS_LOG_INFO ("Partial ACK for seq " << tcpHeader.GetAckNumber () << - " in fast recovery: cwnd set to " << m_tcb->m_cWnd); - m_txBuffer->DiscardUpTo(tcpHeader.GetAckNumber ()); //Bug 1850: retransmit before newack + { + /* Partial ACK. + * In case of partial ACK, retransmit the first unacknowledged + * segment. Deflate the congestion window by the amount of new + * data acknowledged by the Cumulative Acknowledgment field. + * If the partial ACK acknowledges at least one SMSS of new data, + * then add back SMSS bytes to the congestion window. + * This artificially inflates the congestion window in order to + * reflect the additional segment that has left the network. + * Send a new segment if permitted by the new value of cwnd. + * This "partial window deflation" attempts to ensure that, when + * fast recovery eventually ends, approximately ssthresh amount + * of data will be outstanding in the network. Do not exit the + * fast recovery procedure (i.e., if any duplicate ACKs subsequently + * arrive, execute step 4 of Section 3.2 of [RFC5681]). + */ + if (segsAcked >= 1) + { + m_tcb->m_cWnd += m_tcb->m_segmentSize - bytesAcked; + } + else + { + m_tcb->m_cWnd -= bytesAcked; + } + + callCongestionControl = false; // No congestion control on cWnd show be invoked + m_dupAckCount -= segsAcked; // Update the dupAckCount + m_txBuffer->DiscardUpTo (tcpHeader.GetAckNumber ()); //Bug 1850: retransmit before newack DoRetransmit (); // Assume the next seq is lost. Retransmit lost packet + + if (m_isFirstPartialAck) + { + m_isFirstPartialAck = false; + } + + /* This partial ACK acknowledge the fact that one segment has been + * previously lost and now successfully received. All others have + * been processed when they come under the form of dupACKs + */ + m_congestionControl->PktsAcked (m_tcb, 1, m_lastRtt); + + NS_LOG_INFO ("Partial ACK for seq " << tcpHeader.GetAckNumber () << + " in fast recovery: cwnd set to " << m_tcb->m_cWnd << + " recover seq: " << m_recover << + " dupAck count: " << m_dupAckCount); } else if (tcpHeader.GetAckNumber () >= m_recover) {// Full ACK (RFC2582 sec.3 bullet #5 paragraph 2, option 1) - m_tcb->m_cWnd = std::min ( m_tcb->m_ssThresh.Get (), BytesInFlight () + m_tcb->m_segmentSize); - m_ackState = OPEN; + m_tcb->m_cWnd = std::min (m_tcb->m_ssThresh.Get (), + BytesInFlight () + m_tcb->m_segmentSize); + m_isFirstPartialAck = true; + m_dupAckCount = 0; + + /* This FULL ACK acknowledge the fact that one segment has been + * previously lost and now successfully received. All others have + * been processed when they come under the form of dupACKs, + * except the (maybe) new ACKs which come from a new window + */ + m_congestionControl->PktsAcked (m_tcb, segsAcked, m_lastRtt); + newSegsAcked = (tcpHeader.GetAckNumber () - m_recover) / m_tcb->m_segmentSize; + m_tcb->m_ackState = TcpSocketState::OPEN; + NS_LOG_INFO ("Received full ACK for seq " << tcpHeader.GetAckNumber () << ". Leaving fast recovery with cwnd set to " << m_tcb->m_cWnd); NS_LOG_DEBUG ("RECOVERY -> OPEN"); } } - else if (m_ackState == LOSS) + else if (m_tcb->m_ackState == TcpSocketState::LOSS) { // Go back in OPEN state - m_ackState = OPEN; + m_isFirstPartialAck = true; + m_congestionControl->PktsAcked (m_tcb, segsAcked, m_lastRtt); + m_dupAckCount = 0; + m_tcb->m_ackState = TcpSocketState::OPEN; NS_LOG_DEBUG ("LOSS -> OPEN"); } + if (callCongestionControl) + { + m_congestionControl->IncreaseWindow (m_tcb, newSegsAcked); + + NS_LOG_LOGIC ("Congestion control called: " << + " cWnd: " << m_tcb->m_cWnd << + " ssTh: " << m_tcb->m_ssThresh); + } + NewAck (tcpHeader.GetAckNumber ()); - m_dupAckCount = 0; + + // Try to send more data + if (!m_sendPendingDataEvent.IsRunning ()) + { + m_sendPendingDataEvent = Simulator::Schedule (TimeStep (1), + &TcpSocketBase::SendPendingData, + this, m_connected); + } + } // If there is any data piggybacked, store it into m_rxBuffer @@ -2243,6 +2394,11 @@ TcpSocketBase::SendPendingData (bool withAck) " highestRxAck " << m_txBuffer->HeadSequence () << " pd->Size " << m_txBuffer->Size () << " pd->SFS " << m_txBuffer->SizeFromSequence (m_nextTxSequence)); + + NS_LOG_DEBUG ("Window: " << w << + " cWnd: " << m_tcb->m_cWnd << + " unAck: " << UnAckDataCount ()); + uint32_t s = std::min (w, m_tcb->m_segmentSize); // Send no more than window uint32_t sz = SendDataPacket (m_nextTxSequence, s, withAck); nPacketsSent++; // Count sent this loop @@ -2256,38 +2412,39 @@ TcpSocketBase::SendPendingData (bool withAck) } uint32_t -TcpSocketBase::UnAckDataCount () +TcpSocketBase::UnAckDataCount () const { NS_LOG_FUNCTION (this); return m_nextTxSequence.Get () - m_txBuffer->HeadSequence (); } uint32_t -TcpSocketBase::BytesInFlight () +TcpSocketBase::BytesInFlight () const { NS_LOG_FUNCTION (this); return m_highTxMark.Get () - m_txBuffer->HeadSequence (); } uint32_t -TcpSocketBase::Window (void) +TcpSocketBase::Window (void) const { NS_LOG_FUNCTION (this); return std::min (m_rWnd.Get (), m_tcb->m_cWnd.Get ()); } uint32_t -TcpSocketBase::AvailableWindow () +TcpSocketBase::AvailableWindow () const { NS_LOG_FUNCTION_NOARGS (); uint32_t unack = UnAckDataCount (); // Number of outstanding bytes uint32_t win = Window (); // Number of bytes allowed to be outstanding + NS_LOG_DEBUG ("UnAckCount=" << unack << ", Win=" << win); return (win < unack) ? 0 : (win - unack); } uint16_t -TcpSocketBase::AdvertisedWindowSize () +TcpSocketBase::AdvertisedWindowSize () const { uint32_t w = m_rxBuffer->MaxBufferSize () - m_rxBuffer->Size (); @@ -2334,7 +2491,8 @@ TcpSocketBase::ReceivedData (Ptr p, const TcpHeader& tcpHeader) { m_delAckEvent = Simulator::Schedule (m_delAckTimeout, &TcpSocketBase::DelAckTimeout, this); - NS_LOG_LOGIC (this << " scheduled delayed ACK at " << (Simulator::Now () + Simulator::GetDelayLeft (m_delAckEvent)).GetSeconds ()); + NS_LOG_LOGIC (this << " scheduled delayed ACK at " << + (Simulator::Now () + Simulator::GetDelayLeft (m_delAckEvent)).GetSeconds ()); } } // Notify app to receive if necessary @@ -2462,11 +2620,6 @@ TcpSocketBase::NewAck (SequenceNumber32 const& ack) (Simulator::Now () + Simulator::GetDelayLeft (m_retxEvent)).GetSeconds ()); m_retxEvent.Cancel (); } - // Try to send more data - if (!m_sendPendingDataEvent.IsRunning ()) - { - m_sendPendingDataEvent = Simulator::Schedule (TimeStep (1), &TcpSocketBase::SendPendingData, this, m_connected); - } } // Retransmit timeout @@ -2576,15 +2729,29 @@ TcpSocketBase::Retransmit () * retransmission timer and the given segment has already been * retransmitted by way of the retransmission timer at least once, the * value of ssthresh is held constant. + * + * Conditions to decrement slow - start threshold are as follows: + * + * *) The TCP state should be less than disorder, which is nothing but open. + * If we are entering into the loss state from the open state, we have not yet + * reduced the slow - start threshold for the window of data. (Nat: Recovery?) + * *) If we have entered the loss state with all the data pointed to by high_seq + * acknowledged. Once again it means that in whatever state we are (other than + * open state), all the data from the window that got us into the state, prior to + * retransmission timer expiry, has been acknowledged. (Nat: How this can happen?) + * *) If the above two conditions fail, we still have one more condition that can + * demand reducing the slow - start threshold: If we are already in the loss state + * and have not yet retransmitted anything. The condition may arise in case we + * are not able to retransmit anything because of local congestion. */ m_nextTxSequence = m_txBuffer->HeadSequence (); // Restart from highest Ack m_dupAckCount = 0; - if (m_ackState != LOSS) + if (m_tcb->m_ackState != TcpSocketState::LOSS) { - m_ackState = LOSS; - m_tcb->m_ssThresh = GetSsThresh (); + m_tcb->m_ackState = TcpSocketState::LOSS; + m_tcb->m_ssThresh = m_congestionControl->GetSsThresh (m_tcb, BytesInFlight ()); m_tcb->m_cWnd = m_tcb->m_segmentSize; } @@ -2619,12 +2786,13 @@ TcpSocketBase::DoRetransmit () } return; } + // Retransmit a data packet: Call SendDataPacket - NS_LOG_LOGIC ("TcpSocketBase " << this << " retxing seq " << m_txBuffer->HeadSequence ()); uint32_t sz = SendDataPacket (m_txBuffer->HeadSequence (), m_tcb->m_segmentSize, true); // In case of RTO, advance m_nextTxSequence m_nextTxSequence = std::max (m_nextTxSequence.Get (), m_txBuffer->HeadSequence () + sz); + NS_LOG_DEBUG ("retxing seq " << m_txBuffer->HeadSequence ()); } void @@ -2940,9 +3108,9 @@ void TcpSocketBase::UpdateWindowSize (const TcpHeader &header) } // Test for conditions that allow updating of the window - // 1) segment contains new data (advancing the right edge of the receive - // buffer), - // 2) segment does not contain new data but the segment acks new data + // 1) segment contains new data (advancing the right edge of the receive + // buffer), + // 2) segment does not contain new data but the segment acks new data // (highest sequence number acked advances), or // 3) the advertised window is larger than the current send window bool update = false; @@ -3006,12 +3174,6 @@ TcpSocketBase::GetRxBuffer (void) const return m_rxBuffer; } -const char* const -TcpSocketBase::TcpAckStateName[TcpSocketBase::LAST_ACKSTATE] = -{ - "OPEN", "DISORDER", "CWR", "RECOVERY", "LOSS" -}; - void TcpSocketBase::UpdateCwnd (uint32_t oldValue, uint32_t newValue) { @@ -3024,6 +3186,25 @@ TcpSocketBase::UpdateSsThresh (uint32_t oldValue, uint32_t newValue) m_ssThTrace (oldValue, newValue); } +void +TcpSocketBase::UpdateAckState (TcpSocketState::TcpAckState_t oldValue, + TcpSocketState::TcpAckState_t newValue) +{ + m_ackStateTrace (oldValue, newValue); +} + +void +TcpSocketBase::SetCongestionControlAlgorithm (Ptr algo) +{ + NS_LOG_FUNCTION (this << algo); + m_congestionControl = algo; +} + +Ptr +TcpSocketBase::Fork (void) +{ + return CopyObject (this); +} //RttHistory methods RttHistory::RttHistory (SequenceNumber32 s, uint32_t c, Time t) diff --git a/src/internet/model/tcp-socket-base.h b/src/internet/model/tcp-socket-base.h index b4b299914..a2bce489f 100644 --- a/src/internet/model/tcp-socket-base.h +++ b/src/internet/model/tcp-socket-base.h @@ -36,6 +36,7 @@ #include "tcp-tx-buffer.h" #include "tcp-rx-buffer.h" #include "rtt-estimator.h" +#include "tcp-congestion-ops.h" namespace ns3 { @@ -45,7 +46,6 @@ class Node; class Packet; class TcpL4Protocol; class TcpHeader; -class TcpSocketState; /** * \ingroup tcp @@ -76,6 +76,84 @@ public: /// Container for RttHistory objects typedef std::deque RttHistory_t; +/** + * \brief Data structure that records the congestion state of a connection + * + * In this data structure, basic informations that should be passed between + * socket and the congestion control algorithm are saved. Through the code, + * it will be referred as Transmission Control Block (TCB), but there are some + * differencies. In the RFCs, the TCB contains all the variables that defines + * a connection, while we preferred to maintain in this class only the values + * that should be exchanged between socket and other parts, like congestion + * control algorithms. + * + */ +class TcpSocketState : public Object +{ +public: + /** + * Get the type ID. + * \brief Get the type ID. + * \return the object TypeId + */ + static TypeId GetTypeId (void); + + TcpSocketState (); + TcpSocketState (const TcpSocketState &other); + + /** + * \brief Definition of the ACK state machine + * + * The design of this state machine is taken from Linux v4.0, but it has been + * maintained in the Linux mainline from ages. It basically avoids to maintain + * a lot of boolean variables, and it allows to check the transitions from + * different algorithm in a cleaner way. + * + * These states represent the situation from a congestion control point of view: + * in fact, apart the OPEN state, the other states represent a situation in + * which there is a congestion, and different actions should be taken, + * depending on the case. + * + */ + typedef enum + { + OPEN, /**< Normal state, no dubious events */ + DISORDER, /**< In all the respects it is "Open", + * but requires a bit more attention. It is entered when + * we see some SACKs or dupacks. It is split of "Open" */ + CWR, /**< cWnd was reduced due to some Congestion Notification event. + * It can be ECN, ICMP source quench, local device congestion. + * Not used in NS-3 right now. */ + RECOVERY, /**< CWND was reduced, we are fast-retransmitting. */ + LOSS, /**< CWND was reduced due to RTO timeout or SACK reneging. */ + LAST_ACKSTATE /**< Used only in debug messages */ + } TcpAckState_t; + + /** + * \brief Literal names of TCP states for use in log messages + */ + static const char* const TcpAckStateName[TcpSocketState::LAST_ACKSTATE]; + + // Congestion control + TracedValue m_cWnd; //!< Congestion window + TracedValue m_ssThresh; //!< Slow start threshold + uint32_t m_initialCWnd; //!< Initial cWnd value + uint32_t m_initialSsThresh; //!< Initial Slow Start Threshold value + + // Segment + uint32_t m_segmentSize; //!< Segment size + + // Ack state + TracedValue m_ackState; //!< State in the ACK state machine + + /** + * \brief Get cwnd in segments rather than bytes + * + * \return Congestion window in segments + */ + uint32_t GetCwndInSegments () const { return m_cWnd / m_segmentSize; } +}; + /** * \ingroup socket * \ingroup tcp @@ -104,13 +182,22 @@ typedef std::deque RttHistory_t; * Another one (CWR) is present but not used. For more information, see * the TcpAckState_t documentation. * - * * Congestion control interface * --------------------------- * - * The variables needed to congestion control subclasses have been moved inside - * the TcpSocketState class. It contains information on the congestion window, - * slow start threshold, segment size and the state of the Ack state machine. + * Congestion control, unlike older releases of ns-3, has been splitted from + * TcpSocketBase. In particular, each congestion control is now a subclass of + * the main TcpCongestionOps class. Switching between congestion algorithm is + * now a matter of setting a pointer into the TcpSocketBase class. + * + * The variables needed to congestion control classes to operate correctly have + * been moved inside the TcpSocketState class. It contains information on the + * congestion window, slow start threshold, segment size and the state of the + * Ack state machine. + * + * To track the trace inside the TcpSocketState class, a "forward" technique is + * used, which consists in chaining callbacks from TcpSocketState to TcpSocketBase + * (see for example cWnd trace source). * * Fast retransmit * --------------------------- @@ -217,41 +304,6 @@ public: */ Ptr GetRxBuffer (void) const; - /** - * \brief Definition of the ACK state machine - * - * The design of this state machine is taken from Linux v4.0, but it has been - * maintained in the Linux mainline from ages. It basically avoids to maintain - * a lot of boolean variables, and it allows to check the transitions from - * different algorithm in a cleaner way. - * - * These states represent the situation from a congestion control point of view: - * in fact, apart the OPEN state, the other states represent a situation in - * which there is a congestion, and different actions should be taken, - * depending on the case. - * - * \see ReceivedAck - * \see Retransmit - */ - typedef enum - { - OPEN, /**< Normal state, no dubious events */ - DISORDER, /**< In all the respects it is "Open", - * but requires a bit more attention. It is entered when - * we see some SACKs or dupacks. It is split of "Open" */ - CWR, /**< cWnd was reduced due to some Congestion Notification event. - * It can be ECN, ICMP source quench, local device congestion. - * Not used in NS-3 right now. */ - RECOVERY, /**< CWND was reduced, we are fast-retransmitting. */ - LOSS, /**< CWND was reduced due to RTO timeout or SACK reneging. */ - LAST_ACKSTATE /**< Used only in debug messages */ - } TcpAckState_t; - - /** - * \brief Literal names of TCP states for use in log messages - */ - static const char* const TcpAckStateName[TcpSocketBase::LAST_ACKSTATE]; - /** * \brief Callback pointer for cWnd trace chaining */ @@ -262,6 +314,11 @@ public: */ TracedCallback m_ssThTrace; + /** + * \brief Callback pointer for ack state trace chaining + */ + TracedCallback m_ackStateTrace; + /** * \brief Callback function to hook to TcpSocketState congestion window * \param oldValue old cWnd value @@ -276,6 +333,21 @@ public: */ void UpdateSsThresh (uint32_t oldValue, uint32_t newValue); + /** + * \brief Callback function to hook to TcpSocketState ack state + * \param oldValue old ack state value + * \param newValue new ack state value + */ + void UpdateAckState (TcpSocketState::TcpAckState_t oldValue, + TcpSocketState::TcpAckState_t newValue); + + /** + * \brief Install a congestion control algorithm on this socket + * + * \param algo Algorithm to be installed + */ + void SetCongestionControlAlgorithm (Ptr algo); + // Necessary implementations of null functions from ns3::Socket virtual enum SocketErrno GetErrno (void) const; // returns m_errno virtual enum SocketType GetSocketType (void) const; // returns socket type @@ -611,31 +683,31 @@ protected: * \brief Return count of number of unacked bytes * \returns count of number of unacked bytes */ - virtual uint32_t UnAckDataCount (void); + virtual uint32_t UnAckDataCount (void) const; /** * \brief Return total bytes in flight * \returns total bytes in flight */ - virtual uint32_t BytesInFlight (void); + virtual uint32_t BytesInFlight (void) const; /** * \brief Return the max possible number of unacked bytes * \returns the max possible number of unacked bytes */ - virtual uint32_t Window (void); + virtual uint32_t Window (void) const; /** * \brief Return unfilled portion of window * \return unfilled portion of window */ - virtual uint32_t AvailableWindow (void); + virtual uint32_t AvailableWindow (void) const; /** * \brief The amount of Rx window announced to the peer * \returns size of Rx window announced to the peer */ - virtual uint16_t AdvertisedWindowSize (void); + virtual uint16_t AdvertisedWindowSize (void) const; /** * \brief Update the receiver window (RWND) based on the value of the @@ -658,7 +730,7 @@ protected: * \brief Call CopyObject<> to clone me * \returns a copy of the socket */ - virtual Ptr Fork (void) = 0; + virtual Ptr Fork (void); /** * \brief Received an ACK packet @@ -686,11 +758,6 @@ protected: */ virtual void NewAck (SequenceNumber32 const& seq); - /** - * \brief Get the new value of slow start threshold after a loss event - */ - virtual uint32_t GetSsThresh () = 0; - /** * \brief Call Retransmit() upon RTO event */ @@ -864,6 +931,7 @@ protected: TracedValue m_rWnd; //!< Receiver window (RCV.WND in RFC793) TracedValue m_highRxMark; //!< Highest seqno received TracedValue m_highRxAckMark; //!< Highest ack received + uint32_t m_bytesAckedNotProcessed; //!< Bytes acked, but not processed // Options bool m_winScalingEnabled; //!< Window Scale option enabled @@ -875,51 +943,17 @@ protected: EventId m_sendPendingDataEvent; //!< micro-delay event to send pending data - // Ack state - TracedValue m_ackState; //!< State in the ACK state machine - // Fast Retransmit and Recovery SequenceNumber32 m_recover; //!< Previous highest Tx seqnum for fast recovery uint32_t m_retxThresh; //!< Fast Retransmit threshold bool m_limitedTx; //!< perform limited transmit // Transmission Control Block - Ptr m_tcb; //!< Congestion control informations -}; + Ptr m_tcb ; //!< Congestion control informations + Ptr m_congestionControl; //!< Congestion control -/** - * \brief Data structure that records the congestion state of a connection - * - * In this data structure, basic informations that should be passed between - * socket and the congestion control algorithm are saved. Through the code, - * it will be referred as Transmission Control Block (TCB), but there are some - * differencies. In the RFCs, the TCB contains all the variables that defines - * a connection, while we preferred to maintain in this class only the values - * that should be exchanged between socket and other parts, like congestion - * control algorithms. - * - */ -class TcpSocketState : public Object -{ -public: - /** - * Get the type ID. - * \brief Get the type ID. - * \return the object TypeId - */ - static TypeId GetTypeId (void); - - TcpSocketState (); - TcpSocketState (const TcpSocketState &other); - - // Congestion control - TracedValue m_cWnd; //!< Congestion window - TracedValue m_ssThresh; //!< Slow start threshold - uint32_t m_initialCWnd; //!< Initial cWnd value - uint32_t m_initialSsThresh; //!< Initial Slow Start Threshold value - - // Segment - uint32_t m_segmentSize; //!< Segment size + // Guesses over the other connection end + bool m_isFirstPartialAck;//!< First partial ACK during RECOVERY }; /** @@ -929,8 +963,8 @@ public: * \param [in] oldValue original value of the traced variable * \param [in] newValue new value of the traced variable */ -typedef void (* TcpAckStatesTracedValueCallback)(const TcpSocketBase::TcpAckState_t oldValue, - const TcpSocketBase::TcpAckState_t newValue); +typedef void (* TcpAckStatesTracedValueCallback)(const TcpSocketState::TcpAckState_t oldValue, + const TcpSocketState::TcpAckState_t newValue); } // namespace ns3 diff --git a/src/internet/model/tcp-westwood.cc b/src/internet/model/tcp-westwood.cc index 2c9c19e69..9e56ea0df 100644 --- a/src/internet/model/tcp-westwood.cc +++ b/src/internet/model/tcp-westwood.cc @@ -15,8 +15,9 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * - * Authors: Siddharth Gangadhar , Truc Anh N. Nguyen , - * and Greeshma Umapathi + * Authors: Siddharth Gangadhar , + * Truc Anh N. Nguyen , + * Greeshma Umapathi * * James P.G. Sterbenz , director * ResiliNets Research Group http://wiki.ittc.ku.edu/resilinets @@ -30,250 +31,132 @@ * US Department of Defense (DoD), and ITTC at The University of Kansas. */ -#define NS_LOG_APPEND_CONTEXT \ - if (m_node) { std::clog << Simulator::Now ().GetSeconds () << " [node " << m_node->GetId () << "] "; } - #include "tcp-westwood.h" #include "ns3/log.h" -#include "ns3/trace-source-accessor.h" #include "ns3/simulator.h" -#include "ns3/abort.h" -#include "ns3/node.h" -#include "ns3/sequence-number.h" #include "rtt-estimator.h" +#include "tcp-socket-base.h" + +NS_LOG_COMPONENT_DEFINE ("TcpWestwood"); namespace ns3 { -NS_LOG_COMPONENT_DEFINE("TcpWestwood"); - -NS_OBJECT_ENSURE_REGISTERED(TcpWestwood); +NS_OBJECT_ENSURE_REGISTERED (TcpWestwood); TypeId TcpWestwood::GetTypeId (void) { static TypeId tid = TypeId("ns3::TcpWestwood") - .SetParent() - .SetGroupName ("Internet") - .AddConstructor() - .AddAttribute("FilterType", "Use this to choose no filter or Tustin's approximation filter", - EnumValue(TcpWestwood::TUSTIN), MakeEnumAccessor(&TcpWestwood::m_fType), - MakeEnumChecker(TcpWestwood::NONE, "None", TcpWestwood::TUSTIN, "Tustin")) - .AddAttribute("ProtocolType", "Use this to let the code run as Westwood or WestwoodPlus", - EnumValue(TcpWestwood::WESTWOOD), - MakeEnumAccessor(&TcpWestwood::m_pType), - MakeEnumChecker(TcpWestwood::WESTWOOD, "Westwood",TcpWestwood::WESTWOODPLUS, "WestwoodPlus")) - .AddTraceSource("EstimatedBW", "The estimated bandwidth", - MakeTraceSourceAccessor(&TcpWestwood::m_currentBW), - "ns3::TracedValueCallback::Double") + .SetParent() + .SetGroupName ("Internet") + .AddConstructor() + .AddAttribute("FilterType", "Use this to choose no filter or Tustin's approximation filter", + EnumValue(TcpWestwood::TUSTIN), MakeEnumAccessor(&TcpWestwood::m_fType), + MakeEnumChecker(TcpWestwood::NONE, "None", TcpWestwood::TUSTIN, "Tustin")) + .AddAttribute("ProtocolType", "Use this to let the code run as Westwood or WestwoodPlus", + EnumValue(TcpWestwood::WESTWOOD), + MakeEnumAccessor(&TcpWestwood::m_pType), + MakeEnumChecker(TcpWestwood::WESTWOOD, "Westwood",TcpWestwood::WESTWOODPLUS, "WestwoodPlus")) + .AddTraceSource("EstimatedBW", "The estimated bandwidth", + MakeTraceSourceAccessor(&TcpWestwood::m_currentBW), + "ns3::TracedValueCallback::Double") ; return tid; } TcpWestwood::TcpWestwood (void) : - m_currentBW(0), - m_lastSampleBW(0), - m_lastBW(0), - m_minRtt(0), - m_lastAck(0), - m_prevAckNo(0), - m_accountedFor(0), - m_ackedSegments(0), - m_IsCount(false) + TcpNewReno (), + m_currentBW (0), + m_lastSampleBW (0), + m_lastBW (0), + m_minRtt (Time (0)), + m_ackedSegments (0), + m_IsCount (false) { NS_LOG_FUNCTION (this); } TcpWestwood::TcpWestwood (const TcpWestwood& sock) : - TcpSocketBase(sock), - m_currentBW(sock.m_currentBW), - m_lastSampleBW(sock.m_lastSampleBW), - m_lastBW(sock.m_lastBW), - m_minRtt(sock.m_minRtt), - m_lastAck(sock.m_lastAck), - m_prevAckNo(sock.m_prevAckNo), - m_accountedFor(sock.m_accountedFor), - m_pType(sock.m_pType), - m_fType(sock.m_fType), - m_IsCount(sock.m_IsCount) + TcpNewReno (sock), + m_currentBW (sock.m_currentBW), + m_lastSampleBW (sock.m_lastSampleBW), + m_lastBW (sock.m_lastBW), + m_minRtt (Time (0)), + m_pType (sock.m_pType), + m_fType (sock.m_fType), + m_IsCount (sock.m_IsCount) { NS_LOG_FUNCTION (this); NS_LOG_LOGIC ("Invoked the copy constructor"); - NS_LOG_INFO ("m_minRtt at copy constructor" << m_minRtt); } TcpWestwood::~TcpWestwood (void) { } -Ptr -TcpWestwood::Fork (void) -{ - NS_LOG_FUNCTION (this); - return CopyObject(this); -} - void -TcpWestwood::NewAck (const SequenceNumber32& seq) -{ // Same as Reno - NS_LOG_FUNCTION (this << seq); - NS_LOG_LOGIC ("TcpWestwood receieved ACK for seq " << seq << - " cwnd " << m_tcb->m_cWnd << - " ssthresh " << m_tcb->m_ssThresh); +TcpWestwood::PktsAcked (Ptr tcb, uint32_t packetsAcked, + const Time& rtt) +{ + NS_LOG_FUNCTION (this << tcb << packetsAcked << rtt); - // Increase of cwnd based on current phase (slow start or congestion avoidance) - if (m_tcb->m_cWnd < m_tcb->m_ssThresh) - { // Slow start mode, add one segSize to cWnd as in Reno - m_tcb->m_cWnd += m_tcb->m_segmentSize; - NS_LOG_INFO ("In SlowStart, updated to cwnd " << m_tcb->m_cWnd << " ssthresh " << m_tcb->m_ssThresh); + if (rtt.IsZero ()) + { + NS_LOG_WARN ("RTT measured is zero!"); + return; + } + + m_ackedSegments += packetsAcked; + + // Update minRtt + if (m_minRtt.IsZero ()) + { + m_minRtt = rtt; } else - { // Congestion avoidance mode, increase by (segSize*segSize)/cwnd as in Reno - double adder = static_cast (m_tcb->m_segmentSize * m_tcb->m_segmentSize) / m_tcb->m_cWnd.Get(); - adder = std::max(1.0, adder); - m_tcb->m_cWnd += static_cast(adder); - NS_LOG_INFO ("In CongAvoid, updated to cwnd " << m_tcb->m_cWnd << " ssthresh " << m_tcb->m_ssThresh); - } - - // Complete newAck processing - TcpSocketBase::NewAck(seq); -} - -void -TcpWestwood::ReceivedAck (Ptr packet, const TcpHeader& tcpHeader) -{ - NS_LOG_FUNCTION (this); - int acked = 0; - if ((0 != (tcpHeader.GetFlags () & TcpHeader::ACK)) && tcpHeader.GetAckNumber() >= m_prevAckNo) - {// It is a duplicate ACK or a new ACK. Old ACK is ignored. - if (m_pType == TcpWestwood::WESTWOOD) - {// For Westwood, calculate the number of ACKed segments and estimate the BW - acked = CountAck (tcpHeader); - EstimateBW (acked, tcpHeader, Time(0)); - } - else if (m_pType == TcpWestwood::WESTWOODPLUS) - {// For Weswood+, calculate the number of ACKed segments and update m_ackedSegments - if (m_IsCount) - { - acked = CountAck (tcpHeader); - UpdateAckedSegments (acked); - } + { + if (rtt < m_minRtt) + { + m_minRtt = rtt; } } - TcpSocketBase::ReceivedAck (packet, tcpHeader); -} + NS_LOG_LOGIC ("MinRtt: " << m_minRtt.GetMilliSeconds () << "ms"); -void -TcpWestwood::EstimateBW (int acked, const TcpHeader& tcpHeader, Time rtt) -{ - NS_LOG_FUNCTION (this); if (m_pType == TcpWestwood::WESTWOOD) { - // Get the time when the current ACK is received - double currentAck = static_cast (Simulator::Now().GetSeconds()); - // Calculate the BW - m_currentBW = acked * m_tcb->m_segmentSize / (currentAck - m_lastAck); - // Update the last ACK time - m_lastAck = currentAck; + EstimateBW (rtt, tcb); } else if (m_pType == TcpWestwood::WESTWOODPLUS) { - // Calculate the BW - m_currentBW = m_ackedSegments * m_tcb->m_segmentSize / rtt.GetSeconds(); - // Reset m_ackedSegments and m_IsCount for the next sampling - m_ackedSegments = 0; + if (!(rtt.IsZero () || m_IsCount)) + { + m_IsCount = true; + m_bwEstimateEvent.Cancel (); + m_bwEstimateEvent = Simulator::Schedule (rtt, &TcpWestwood::EstimateBW, + this, rtt, tcb); + } + } +} + +void +TcpWestwood::EstimateBW (const Time &rtt, Ptr tcb) +{ + NS_LOG_FUNCTION (this); + + NS_ASSERT (!rtt.IsZero ()); + + m_currentBW = m_ackedSegments * tcb->m_segmentSize / rtt.GetSeconds (); + + if (m_pType == TcpWestwood::WESTWOODPLUS) + { m_IsCount = false; } + m_ackedSegments = 0; + NS_LOG_LOGIC ("Estimated BW: " << m_currentBW); + // Filter the BW sample - Filtering(); -} - -int -TcpWestwood::CountAck (const TcpHeader& tcpHeader) -{ - NS_LOG_FUNCTION (this); - - // Calculate the number of acknowledged segments based on the received ACK number - int cumul_ack = (tcpHeader.GetAckNumber() - m_prevAckNo) / m_tcb->m_segmentSize; - - if (cumul_ack == 0) - {// A DUPACK counts for 1 segment delivered successfully - m_accountedFor++; - cumul_ack = 1; - } - if (cumul_ack > 1) - {// A delayed ACK or a cumulative ACK after a retransmission - // Check how much new data it ACKs - if (m_accountedFor >= cumul_ack) - { - m_accountedFor -= cumul_ack; - cumul_ack = 1; - } - else if (m_accountedFor < cumul_ack) - { - cumul_ack -= m_accountedFor; - m_accountedFor = 0; - } - } - - // Update the previous ACK number - m_prevAckNo = tcpHeader.GetAckNumber(); - - return cumul_ack; -} - -void -TcpWestwood::UpdateAckedSegments (int acked) -{ - m_ackedSegments += acked; -} - - -uint32_t -TcpWestwood::GetSsThresh () -{ - return uint32_t(m_currentBW * static_cast (m_minRtt.GetSeconds())); -} - -void -TcpWestwood::EstimateRtt (const TcpHeader& tcpHeader) -{ - NS_LOG_FUNCTION_NOARGS (); - - // Calculate m_lastRtt - TcpSocketBase::EstimateRtt (tcpHeader); - - // Update minRtt - if (m_minRtt == Time (0)) - { - m_minRtt = m_lastRtt; - } - else - { - if (m_lastRtt < m_minRtt) - { - m_minRtt = m_lastRtt; - } - } - - // For Westwood+, start running a clock on the currently estimated RTT if possible - // to trigger a new BW sampling event - if (m_pType == TcpWestwood::WESTWOODPLUS) - { - if(m_lastRtt != Time (0) && m_state == ESTABLISHED && !m_IsCount) - { - m_IsCount = true; - m_bwEstimateEvent.Cancel(); - m_bwEstimateEvent = Simulator::Schedule (m_lastRtt, &TcpWestwood::EstimateBW,this,m_ackedSegments,tcpHeader,m_lastRtt); - } - } -} - -void -TcpWestwood::Filtering () -{ - NS_LOG_FUNCTION (this); double alpha = 0.9; @@ -287,6 +170,27 @@ TcpWestwood::Filtering () m_lastSampleBW = sample_bwe; m_lastBW = m_currentBW; } + + NS_LOG_LOGIC ("Estimated BW after filtering: " << m_currentBW); +} + +uint32_t +TcpWestwood::GetSsThresh (Ptr tcb, + uint32_t bytesInFlight) +{ + (void) bytesInFlight; + NS_LOG_LOGIC ("CurrentBW: " << m_currentBW << " minRtt: " << + m_minRtt << " ssthresh: " << + m_currentBW * static_cast (m_minRtt.GetSeconds ())); + + return std::max (2*tcb->m_segmentSize, + uint32_t (m_currentBW * static_cast (m_minRtt.GetSeconds ()))); +} + +Ptr +TcpWestwood::Fork () +{ + return CreateObject (*this); } } // namespace ns3 diff --git a/src/internet/model/tcp-westwood.h b/src/internet/model/tcp-westwood.h index 919378187..39df7e9ab 100644 --- a/src/internet/model/tcp-westwood.h +++ b/src/internet/model/tcp-westwood.h @@ -33,11 +33,17 @@ #ifndef TCP_WESTWOOD_H #define TCP_WESTWOOD_H -#include "tcp-socket-base.h" -#include "ns3/packet.h" +#include "tcp-congestion-ops.h" +#include "ns3/sequence-number.h" +#include "ns3/traced-value.h" namespace ns3 { +class Packet; +class TcpHeader; +class Time; +class EventId; + /** * \ingroup socket * \ingroup tcp @@ -57,9 +63,9 @@ namespace ns3 { * and the EstimateBW (int, const, Time). The CountAck method calculates * the number of acknowledged segments on the receipt of an ACK. * The EstimateBW estimates the bandwidth based on the value returned by CountAck - * and the sampling interval (last ACK inter-arrival time for Westwood and last RTT for Westwood+). + * and the sampling interval (last ACK inter-arrival time for Westwood and last RTT for Westwood+). */ -class TcpWestwood : public TcpSocketBase +class TcpWestwood : public TcpNewReno { public: /** @@ -94,35 +100,15 @@ public: TUSTIN }; -protected: - virtual Ptr Fork (void); // Call CopyObject to clone me - virtual void NewAck (SequenceNumber32 const& seq); // Inc cwnd and call NewAck() of parent - virtual uint32_t GetSsThresh (); + virtual uint32_t GetSsThresh (Ptr tcb, + uint32_t bytesInFlight); - /** - * Process the newly received ACK - * - * \param packet the received ACK packet - * \param tcpHeader the header attached to the ACK packet - */ - virtual void ReceivedAck (Ptr packet, const TcpHeader& tcpHeader); - - /** - * Estimate the RTT, record the minimum value, - * and run a clock on the RTT to trigger Westwood+ bandwidth sampling - * \param header the packet header - */ - virtual void EstimateRtt (const TcpHeader& header); + virtual void PktsAcked (Ptr tcb, uint32_t packetsAcked, + const Time& rtt); + + virtual Ptr Fork (); private: - /** - * Calculate the number of acknowledged packets upon the receipt of an ACK packet - * - * \param tcpHeader the header of the received ACK packet - * \return the number of ACKed packets - */ - int CountAck (const TcpHeader& tcpHeader); - /** * Update the total number of acknowledged packets during the current RTT * @@ -133,25 +119,16 @@ private: /** * Estimate the network's bandwidth * - * \param acked the number of acknowledged packets returned by CountAck - * \param tcpHeader the header of the packet + * \param acked the number of acknowledged packets * \param rtt the RTT estimation */ - void EstimateBW (int acked, const TcpHeader& tcpHeader, Time rtt); - - /** - * Tustin filter - */ - void Filtering (void); + void EstimateBW (const Time& rtt, Ptr tcb); protected: TracedValue m_currentBW; //!< Current value of the estimated BW double m_lastSampleBW; //!< Last bandwidth sample double m_lastBW; //!< Last bandwidth sample after being filtered Time m_minRtt; //!< Minimum RTT - double m_lastAck; //!< The time last ACK was received - SequenceNumber32 m_prevAckNo; //!< Previously received ACK number - int m_accountedFor; //!< The number of received DUPACKs enum ProtocolType m_pType; //!< 0 for Westwood, 1 for Westwood+ enum FilterType m_fType; //!< 0 for none, 1 for Tustin diff --git a/src/internet/test/tcp-header-test.cc b/src/internet/test/tcp-header-test.cc index 25abd9772..0f492d2fd 100644 --- a/src/internet/test/tcp-header-test.cc +++ b/src/internet/test/tcp-header-test.cc @@ -23,7 +23,7 @@ #include "ns3/core-module.h" #include "ns3/tcp-header.h" #include "ns3/buffer.h" -#include "ns3/private/tcp-option-rfc793.h" +#include "../model/tcp-option-rfc793.h" namespace ns3 { diff --git a/src/internet/wscript b/src/internet/wscript index f9341ae09..7ca5248d2 100644 --- a/src/internet/wscript +++ b/src/internet/wscript @@ -145,7 +145,7 @@ def build(bld): 'model/ipv6-option-demux.cc', 'model/icmpv6-l4-protocol.cc', 'model/tcp-socket-base.cc', - 'model/tcp-newreno.cc', + 'model/tcp-congestion-ops.cc', 'model/tcp-westwood.cc', 'model/tcp-rx-buffer.cc', 'model/tcp-tx-buffer.cc', @@ -321,7 +321,7 @@ def build(bld): 'helper/ipv6-interface-container.h', 'helper/ipv6-routing-helper.h', 'model/ipv6-address-generator.h', - 'model/tcp-newreno.h', + 'model/tcp-congestion-ops.h', 'model/tcp-westwood.h', 'model/tcp-socket-base.h', 'model/tcp-tx-buffer.h',