diff --git a/src/internet/doc/tcp.rst b/src/internet/doc/tcp.rst index d6d42bb5c8c..b76b1aaf9a8 100644 --- a/src/internet/doc/tcp.rst +++ b/src/internet/doc/tcp.rst @@ -1146,6 +1146,34 @@ ExitRecovery is called just prior to exiting recovery phase in order to perform required congestion window ajustments. UpdateBytesSent is used to keep track of bytes sent and is called whenever a data packet is sent during recovery phase. +Delivery Rate Estimation +++++++++++++++++++++++++ +Current TCP implementation measures the approximate value of the delivery rate of +inflight data based on Delivery Rate Estimation. + +As high level idea, keep in mind that the algorithm keeps track of 2 variables: + +1. `delivered`: Total amount of data delivered so far. + +2. `deliveredStamp`: Last time `delivered` was updated. + +When a packet is transmitted, the value of `delivered (d0)` and `deliveredStamp (t0)` +is stored in its respective TcpTxItem. + +When an acknowledgement comes for this packet, the value of `delivered` and `deliveredStamp` +is updated to `d1` and `t1` in the same TcpTxItem. + +After processing the acknowledgement, the rate sample is calculated and then passed +to a congestion avoidance algorithm: + +.. math:: delivery_rate = (d1 - d0)/(t1 - t0) + + +The implementation to estimate delivery rate is a joint work between TcpTxBuffer and TcpRateOps. +For more information, please take a look at their doxygen documentation. + +The implementation follows the RFC (Delivery Rate Estimation): https://tools.ietf.org/html/draft-cheng-iccrg-delivery-rate-estimation-00 + Current limitations +++++++++++++++++++ diff --git a/src/internet/model/tcp-congestion-ops.h b/src/internet/model/tcp-congestion-ops.h index 6b2bd6f15f9..02e7b2133d3 100644 --- a/src/internet/model/tcp-congestion-ops.h +++ b/src/internet/model/tcp-congestion-ops.h @@ -20,6 +20,7 @@ #define TCPCONGESTIONOPS_H #include "ns3/tcp-socket-state.h" +#include "ns3/tcp-rate-ops.h" namespace ns3 { @@ -101,7 +102,11 @@ class TcpCongestionOps : public Object * \param tcb internal congestion state * \param segmentsAcked count of segments acked */ - virtual void IncreaseWindow (Ptr tcb, uint32_t segmentsAcked) = 0; + virtual void IncreaseWindow (Ptr tcb, uint32_t segmentsAcked) + { + NS_UNUSED (tcb); + NS_UNUSED (segmentsAcked); + } /** * \brief Timing information on received ACK @@ -154,6 +159,36 @@ class TcpCongestionOps : public Object NS_UNUSED (tcb); NS_UNUSED (event); } + + /** + * \brief Returns true when Congestion Control Algorithm implements CongControl + * + * \return true if CC implements CongControl function + */ + virtual bool HasCongControl () const + { + return false; + } + + /** + * \brief Called when packets are delivered to update cwnd and pacing rate + * + * This function mimics the function cong_control in Linux. It is allowed to + * change directly cWnd and pacing rate. + * + * \param tcb internal congestion state + * \param rc Rate information for the connection + * \param rs Rate sample (over a period of time) information + */ + virtual void CongControl (Ptr tcb, + const TcpRateOps::TcpRateConnection &rc, + const TcpRateOps::TcpRateSample &rs) + { + NS_UNUSED (tcb); + NS_UNUSED (rc); + NS_UNUSED (rs); + } + // Present in Linux but not in ns-3 yet: /* call when ack arrives (optional) */ // void (*in_ack_event)(struct sock *sk, u32 flags); diff --git a/src/internet/model/tcp-prr-recovery.cc b/src/internet/model/tcp-prr-recovery.cc index ba724fd42b5..a43b4fabcf5 100644 --- a/src/internet/model/tcp-prr-recovery.cc +++ b/src/internet/model/tcp-prr-recovery.cc @@ -22,7 +22,6 @@ */ #include "tcp-prr-recovery.h" -#include "ns3/tcp-socket-base.h" #include "ns3/tcp-congestion-ops.h" #include "ns3/log.h" @@ -58,7 +57,6 @@ TcpPrrRecovery::TcpPrrRecovery (const TcpPrrRecovery& recovery) m_prrDelivered (recovery.m_prrDelivered), m_prrOut (recovery.m_prrOut), m_recoveryFlightSize (recovery.m_recoveryFlightSize), - m_previousSackedBytes (recovery.m_previousSackedBytes), m_reductionBoundMode (recovery.m_reductionBoundMode) { NS_LOG_FUNCTION (this); @@ -71,29 +69,23 @@ TcpPrrRecovery::~TcpPrrRecovery (void) void TcpPrrRecovery::EnterRecovery (Ptr tcb, uint32_t dupAckCount, - uint32_t unAckDataCount, uint32_t lastSackedBytes) + uint32_t unAckDataCount, uint32_t deliveredBytes) { - NS_LOG_FUNCTION (this << tcb << dupAckCount << unAckDataCount << lastSackedBytes); + NS_LOG_FUNCTION (this << tcb << dupAckCount << unAckDataCount); NS_UNUSED (dupAckCount); m_prrOut = 0; m_prrDelivered = 0; m_recoveryFlightSize = unAckDataCount; - m_previousSackedBytes = lastSackedBytes; - DoRecovery (tcb, 0, lastSackedBytes); + DoRecovery (tcb, deliveredBytes); } void -TcpPrrRecovery::DoRecovery (Ptr tcb, uint32_t lastAckedBytes, - uint32_t lastSackedBytes) +TcpPrrRecovery::DoRecovery (Ptr tcb, uint32_t deliveredBytes) { - NS_LOG_FUNCTION (this << tcb << lastAckedBytes << lastSackedBytes); - uint32_t lastDeliveredBytes; - int changeInSackedBytes = int (lastSackedBytes - m_previousSackedBytes); - lastDeliveredBytes = lastAckedBytes + changeInSackedBytes > 0 ? lastAckedBytes + changeInSackedBytes : 0; - m_previousSackedBytes = lastSackedBytes; - m_prrDelivered += lastDeliveredBytes; + NS_LOG_FUNCTION (this << tcb << deliveredBytes); + m_prrDelivered += deliveredBytes; int sendCount; if (tcb->m_bytesInFlight > tcb->m_ssThresh) @@ -109,7 +101,7 @@ TcpPrrRecovery::DoRecovery (Ptr tcb, uint32_t lastAckedBytes, } else if (m_reductionBoundMode == SSRB) { - limit = std::max (m_prrDelivered - m_prrOut, lastDeliveredBytes) + tcb->m_segmentSize; + limit = std::max (m_prrDelivered - m_prrOut, deliveredBytes) + tcb->m_segmentSize; } sendCount = std::min (limit, static_cast (tcb->m_ssThresh - tcb->m_bytesInFlight)); } diff --git a/src/internet/model/tcp-prr-recovery.h b/src/internet/model/tcp-prr-recovery.h index eb9d5c97f2a..d0b94227df4 100644 --- a/src/internet/model/tcp-prr-recovery.h +++ b/src/internet/model/tcp-prr-recovery.h @@ -72,10 +72,9 @@ class TcpPrrRecovery : public TcpClassicRecovery std::string GetName () const override; virtual void EnterRecovery (Ptr tcb, uint32_t dupAckCount, - uint32_t unAckDataCount, uint32_t lastSackedBytes) override; + uint32_t unAckDataCount, uint32_t deliveredBytes) override; - virtual void DoRecovery (Ptr tcb, uint32_t lastAckedBytes, - uint32_t lastSackedBytes) override; + virtual void DoRecovery (Ptr tcb, uint32_t deliveredBytes) override; virtual void ExitRecovery (Ptr tcb) override; @@ -87,7 +86,6 @@ class TcpPrrRecovery : public TcpClassicRecovery uint32_t m_prrDelivered {0}; //!< total bytes delivered during recovery phase uint32_t m_prrOut {0}; //!< total bytes sent during recovery phase uint32_t m_recoveryFlightSize {0}; //!< value of bytesInFlight at the start of recovery phase - uint32_t m_previousSackedBytes {0}; //!< total bytes SACKed by the previous ACK ReductionBound_t m_reductionBoundMode {SSRB}; //!< mode of Reduction Bound to be used }; } // namespace ns3 diff --git a/src/internet/model/tcp-rate-ops.cc b/src/internet/model/tcp-rate-ops.cc new file mode 100644 index 00000000000..e4d31f89091 --- /dev/null +++ b/src/internet/model/tcp-rate-ops.cc @@ -0,0 +1,292 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2018 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 + * + */ +#include "tcp-rate-ops.h" +#include "ns3/log.h" +#include "ns3/simulator.h" + +namespace ns3 { + +NS_OBJECT_ENSURE_REGISTERED (TcpRateOps); + +TypeId +TcpRateOps::GetTypeId (void) +{ + static TypeId tid = TypeId ("ns3::TcpRateOps") + .SetParent () + .SetGroupName ("Internet") + ; + return tid; +} + +NS_LOG_COMPONENT_DEFINE ("TcpRateLinux"); +NS_OBJECT_ENSURE_REGISTERED (TcpRateLinux); + +TypeId +TcpRateLinux::GetTypeId (void) +{ + static TypeId tid = TypeId ("ns3::TcpRateLinux") + .SetParent () + .SetGroupName ("Internet") + .AddTraceSource ("TcpRateUpdated", + "Tcp rate information has been updated", + MakeTraceSourceAccessor (&TcpRateLinux::m_rateTrace), + "ns3::TcpRateLinux::TcpRateUpdated") + .AddTraceSource ("TcpRateSampleUpdated", + "Tcp rate sample has been updated", + MakeTraceSourceAccessor (&TcpRateLinux::m_rateSampleTrace), + "ns3::TcpRateLinux::TcpRateSampleUpdated") + ; + return tid; +} + +const TcpRateOps::TcpRateSample & +TcpRateLinux::SampleGen (uint32_t delivered, uint32_t lost, bool is_sack_reneg, + uint32_t priorInFlight, const Time &minRtt) +{ + NS_LOG_FUNCTION (this << delivered << lost << is_sack_reneg); + + /* Clear app limited if bubble is acked and gone. */ + if (m_rate.m_appLimited != 0 && m_rate.m_delivered > m_rate.m_appLimited) + { + NS_LOG_INFO ("Updating Rate m_appLimited to zero"); + m_rate.m_appLimited = 0; + } + + NS_LOG_INFO ("Updating RateSample m_ackedSacked=" << delivered << + ", m_bytesLoss=" << lost << " and m_priorInFlight" << priorInFlight); + m_rateSample.m_ackedSacked = delivered; /* freshly ACKed or SACKed */ + m_rateSample.m_bytesLoss = lost; /* freshly marked lost */ + m_rateSample.m_priorInFlight = priorInFlight; + + /* Return an invalid sample if no timing information is available or + * in recovery from loss with SACK reneging. Rate samples taken during + * a SACK reneging event may overestimate bw by including packets that + * were SACKed before the reneg. + */ + if (m_rateSample.m_priorTime == Seconds (0) || is_sack_reneg) + { + NS_LOG_INFO ("PriorTime is zero, invalidating sample"); + m_rateSample.m_delivered = -1; + m_rateSample.m_interval = Seconds (0); + m_rateSampleTrace (m_rateSample); + return m_rateSample; + } + + // LINUX: + // /* Model sending data and receiving ACKs as separate pipeline phases + // * for a window. Usually the ACK phase is longer, but with ACK + // * compression the send phase can be longer. To be safe we use the + // * longer phase. + // */ + // auto snd_us = m_rateSample.m_interval; /* send phase */ + // auto ack_us = Simulator::Now () - m_rateSample.m_prior_mstamp; + // m_rateSample.m_interval = std::max (snd_us, ack_us); + + m_rateSample.m_interval = std::max (m_rateSample.m_sendElapsed, m_rateSample.m_ackElapsed); + m_rateSample.m_delivered = m_rate.m_delivered - m_rateSample.m_priorDelivered; + NS_LOG_INFO ("Updating sample interval=" << m_rateSample.m_interval << " and delivered data" << m_rateSample.m_delivered); + + /* Normally we expect m_interval >= minRtt. + * Note that rate may still be over-estimated when a spuriously + * retransmistted skb was first (s)acked because "interval_us" + * is under-estimated (up to an RTT). However continuously + * measuring the delivery rate during loss recovery is crucial + * for connections suffer heavy or prolonged losses. + */ + if (m_rateSample.m_interval < minRtt) + { + NS_LOG_INFO ("Sampling interval is invalid"); + m_rateSample.m_interval = Seconds (0); + m_rateSample.m_priorTime = Seconds (0); // To make rate sample invalid + m_rateSampleTrace (m_rateSample); + return m_rateSample; + } + + /* Record the last non-app-limited or the highest app-limited bw */ + if (!m_rateSample.m_isAppLimited || + (m_rateSample.m_delivered * m_rate.m_rateInterval >= + m_rate.m_rateDelivered * m_rateSample.m_interval)) + { + m_rate.m_rateDelivered = m_rateSample.m_delivered; + m_rate.m_rateInterval = m_rateSample.m_interval; + m_rate.m_rateAppLimited = m_rateSample.m_isAppLimited; + m_rateSample.m_deliveryRate = DataRate (m_rateSample.m_delivered * 8.0 / m_rateSample.m_interval.GetSeconds ()); + NS_LOG_INFO ("Updating delivery rate=" << m_rateSample.m_deliveryRate); + } + + m_rateSampleTrace (m_rateSample); + return m_rateSample; +} + +void +TcpRateLinux::CalculateAppLimited (uint32_t cWnd, uint32_t in_flight, + uint32_t segmentSize, const SequenceNumber32 &tailSeq, + const SequenceNumber32 &nextTx, const uint32_t lostOut, + const uint32_t retransOut) +{ + NS_LOG_FUNCTION (this); + + /* Missing checks from Linux: + * - Nothing in sending host's qdisc queues or NIC tx queue. NOT IMPLEMENTED + */ + if (tailSeq - nextTx < static_cast (segmentSize) && // We have less than one packet to send. + in_flight < cWnd && // We are not limited by CWND. + lostOut <= retransOut) // All lost packets have been retransmitted. + { + m_rate.m_appLimited = std::max (m_rate.m_delivered + in_flight, 1UL); + m_rateTrace (m_rate); + } + + // m_appLimited will be reset once in SampleGen, if it has to be. + // else + // { + // m_rate.m_appLimited = 0U; + // } +} + +void +TcpRateLinux::SkbDelivered (TcpTxItem * skb) +{ + NS_LOG_FUNCTION (this << skb); + + TcpTxItem::RateInformation & skbInfo = skb->GetRateInformation (); + + if (skbInfo.m_deliveredTime == Time::Max ()) + { + return; + } + + m_rate.m_delivered += skb->GetSeqSize (); + m_rate.m_deliveredTime = Simulator::Now (); + + if (m_rateSample.m_priorDelivered == 0 || + skbInfo.m_delivered > m_rateSample.m_priorDelivered) + { + m_rateSample.m_priorDelivered = skbInfo.m_delivered; + m_rateSample.m_priorTime = skbInfo.m_deliveredTime; + m_rateSample.m_isAppLimited = skbInfo.m_isAppLimited; + m_rateSample.m_sendElapsed = skb->GetLastSent () - skbInfo.m_firstSent; + m_rateSample.m_ackElapsed = Simulator::Now () - skbInfo.m_deliveredTime; + + m_rateSampleTrace (m_rateSample); + + m_rate.m_firstSentTime = skb->GetLastSent (); + } + + /* Mark off the skb delivered once it's taken into account to avoid being + * used again when it's cumulatively acked, in case it was SACKed. + */ + skbInfo.m_deliveredTime = Time::Max (); + m_rate.m_txItemDelivered = skbInfo.m_delivered; + m_rateTrace (m_rate); +} + +void +TcpRateLinux::SkbSent (TcpTxItem *skb, bool isStartOfTransmission) +{ + NS_LOG_FUNCTION (this << skb << isStartOfTransmission); + + TcpTxItem::RateInformation & skbInfo = skb->GetRateInformation (); + + /* In general we need to start delivery rate samples from the + * time we received the most recent ACK, to ensure we include + * the full time the network needs to deliver all in-flight + * packets. If there are no packets in flight yet, then we + * know that any ACKs after now indicate that the network was + * able to deliver those packets completely in the sampling + * interval between now and the next ACK. + * + * Note that we use the entire window size instead of bytes_in_flight + * because the latter is a guess based on RTO and loss-marking + * heuristics. We don't want spurious RTOs or loss markings to cause + * a spuriously small time interval, causing a spuriously high + * bandwidth estimate. + */ + if (isStartOfTransmission) + { + NS_LOG_INFO ("Starting of a transmission at time " << Simulator::Now ().GetSeconds ()); + m_rate.m_firstSentTime = Simulator::Now (); + m_rate.m_deliveredTime = Simulator::Now (); + m_rateTrace (m_rate); + } + + skbInfo.m_firstSent = m_rate.m_firstSentTime; + skbInfo.m_deliveredTime = m_rate.m_deliveredTime; + skbInfo.m_isAppLimited = (m_rate.m_appLimited != 0); + skbInfo.m_delivered = m_rate.m_delivered; +} + +std::ostream & +operator<< (std::ostream & os, TcpRateLinux::TcpRateConnection const & rate) +{ + os << "m_delivered = " << rate.m_delivered << std::endl; + os << "m_deliveredTime = " << rate.m_deliveredTime << std::endl; + os << "m_firstSentTime = " << rate.m_firstSentTime << std::endl; + os << "m_appLimited = " << rate.m_appLimited << std::endl; + os << "m_rateDelivered = " << rate.m_rateDelivered << std::endl; + os << "m_rateInterval = " << rate.m_rateInterval << std::endl; + os << "m_rateAppLimited = " << rate.m_rateAppLimited << std::endl; + os << "m_txItemDelivered = " << rate.m_txItemDelivered << std::endl; + return os; +} + +std::ostream & +operator<< (std::ostream & os, TcpRateLinux::TcpRateSample const & sample) +{ + os << "m_deliveryRate = " << sample.m_deliveryRate << std::endl; + os << " m_isAppLimited = " << sample.m_isAppLimited << std::endl; + os << " m_interval = " << sample.m_interval << std::endl; + os << " m_delivered = " << sample.m_delivered << std::endl; + os << " m_priorDelivered = " << sample.m_priorDelivered << std::endl; + os << " m_priorTime = " << sample.m_priorTime << std::endl; + os << " m_sendElapsed = " << sample.m_sendElapsed << std::endl; + os << " m_ackElapsed = " << sample.m_ackElapsed << std::endl; + os << " m_bytesLoss = " << sample.m_bytesLoss << std::endl; + os << " m_priorInFlight= " << sample.m_priorInFlight << std::endl; + os << " m_ackedSacked = " << sample.m_ackedSacked << std::endl; + return os; +} + +bool +operator== (TcpRateLinux::TcpRateSample const & lhs, TcpRateLinux::TcpRateSample const & rhs) +{ + return (lhs.m_deliveryRate == rhs.m_deliveryRate && + lhs.m_isAppLimited == rhs.m_isAppLimited && + lhs.m_interval == rhs.m_interval && + lhs.m_delivered == rhs.m_delivered && + lhs.m_priorDelivered == rhs.m_priorDelivered && + lhs.m_priorTime == rhs.m_priorTime && + lhs.m_sendElapsed == rhs.m_sendElapsed && + lhs.m_ackElapsed == rhs.m_ackElapsed + ); +} + +bool +operator== (TcpRateLinux::TcpRateConnection const & lhs, + TcpRateLinux::TcpRateConnection const & rhs) +{ + return (lhs.m_delivered == rhs.m_delivered && + lhs.m_deliveredTime == rhs.m_deliveredTime && + lhs.m_firstSentTime == rhs.m_firstSentTime && + lhs.m_appLimited == rhs.m_appLimited + ); +} + + +} // namespace ns3 diff --git a/src/internet/model/tcp-rate-ops.h b/src/internet/model/tcp-rate-ops.h new file mode 100644 index 00000000000..76603841217 --- /dev/null +++ b/src/internet/model/tcp-rate-ops.h @@ -0,0 +1,260 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2018 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 + * + */ +#pragma once + +#include "ns3/object.h" +#include "ns3/tcp-tx-item.h" +#include "ns3/traced-callback.h" +#include "ns3/data-rate.h" +#include "ns3/traced-value.h" + +namespace ns3 { + +/** + * \brief Interface for all operations that involve a Rate monitoring for TCP. + * + * The interface is meant to take information to generate rate information + * valid for congestion avoidance algorithm such as BBR. + * + * Please take a look to the TcpRateLinux class for an example. + */ +class TcpRateOps : public Object +{ +public: + struct TcpRateSample; + struct TcpRateConnection; + + /** + * Get the type ID. + * \brief Get the type ID. + * \return the object TypeId + */ + static TypeId GetTypeId (void); + /** + * \brief Put the rate information inside the sent skb + * + * Snapshot the current delivery information in the skb, to generate + * a rate sample later when the skb is (s)acked in SkbDelivered (). + * + * \param skb The SKB sent + * \param isStartOfTransmission true if this is a start of transmission + * (i.e., in_flight == 0) + */ + virtual void SkbSent (TcpTxItem *skb, bool isStartOfTransmission) = 0; + + /** + * \brief Update the Rate information after an item is received + * + * When an skb is sacked or acked, we fill in the rate sample with the (prior) + * delivery information when the skb was last transmitted. + * + * If an ACK (s)acks multiple skbs (e.g., stretched-acks), this function is + * called multiple times. We favor the information from the most recently + * sent skb, i.e., the skb with the highest prior_delivered count. + * + * \param skb The SKB delivered ((s)ACKed) + */ + virtual void SkbDelivered (TcpTxItem * skb) = 0; + + /** + * \brief If a gap is detected between sends, it means we are app-limited. + * \return TODO What the Linux kernel is setting in tp->app_limited? + * https://elixir.bootlin.com/linux/latest/source/net/ipv4/tcp_rate.c#L177 + * + * \param cWnd Congestion Window + * \param in_flight In Flight size (in bytes) + * \param segmentSize Segment size + * \param tailSeq Tail Sequence + * \param nextTx NextTx + * \param lostOut Number of lost bytes + * \param retransOut Number of retransmitted bytes + */ + virtual void CalculateAppLimited (uint32_t cWnd, uint32_t in_flight, + uint32_t segmentSize, const SequenceNumber32 &tailSeq, + const SequenceNumber32 &nextTx, const uint32_t lostOut, + const uint32_t retransOut) = 0; + + /** + * + * \brief Generate a TcpRateSample to feed a congestion avoidance algorithm. + * + * This function will be called after an ACK (or a SACK) is received. The + * (S)ACK carries some implicit information, such as how many segments have been + * lost or delivered. These values will be this function input. + * + * \param delivered number of delivered segments (e.g., receiving a cumulative + * ACK means having more than 1 segment delivered) relative to the most recent + * (S)ACK received + * \param lost number of segments that we detected as lost after the reception + * of the most recent (S)ACK + * \param is_sack_reneg Is SACK reneged? + * \param minRtt Minimum RTT so far + * \return The TcpRateSample that will be used for CA + */ + virtual const TcpRateSample & SampleGen (uint32_t delivered, uint32_t lost, + bool is_sack_reneg, uint32_t priorInFlight, + const Time &minRtt) = 0; + + /** + * \return The information about the rate connection + * + */ + virtual const TcpRateConnection & GetConnectionRate () = 0; + + /** + * \brief Rate Sample structure + * + * A rate sample measures the number of (original/retransmitted) data + * packets delivered "delivered" over an interval of time "interval_us". + * The tcp_rate code fills in the rate sample, and congestion + * control modules that define a cong_control function to run at the end + * of ACK processing can optionally chose to consult this sample when + * setting cwnd and pacing rate. + * A sample is invalid if "delivered" or "interval_us" is negative. + */ + struct TcpRateSample + { + DataRate m_deliveryRate {DataRate ("0bps")};//!< The delivery rate sample + bool m_isAppLimited {false}; //!< Indicates whether the rate sample is application-limited + Time m_interval {Seconds (0.0)}; //!< The length of the sampling interval + int32_t m_delivered {0}; //!< The amount of data marked as delivered over the sampling interval + uint32_t m_priorDelivered {0}; //!< The delivered count of the most recent packet delivered + Time m_priorTime {Seconds (0.0)}; //!< The delivered time of the most recent packet delivered + Time m_sendElapsed {Seconds (0.0)}; //!< Send time interval calculated from the most recent packet delivered + Time m_ackElapsed {Seconds (0.0)}; //!< ACK time interval calculated from the most recent packet delivered + uint32_t m_bytesLoss {0}; //!< The amount of data marked as lost from the most recent ack received + uint32_t m_priorInFlight {0}; //!< The value if bytes in flight prior to last received ack + uint32_t m_ackedSacked {0}; //!< The amount of data acked and sacked in the last received ack + /** + * \brief Is the sample valid? + * \return true if the sample is valid, false otherwise. + */ + bool IsValid () const + { + return (m_priorTime != Seconds (0.0) || m_interval != Seconds (0.0)); + } + }; + + /** + * \brief Information about the connection rate + * + * In this struct, the values are for the entire connection, and not just + * for an interval of time + */ + struct TcpRateConnection + { + uint64_t m_delivered {0}; //!< The total amount of data in bytes delivered so far + Time m_deliveredTime {Seconds (0)}; //!< Simulator time when m_delivered was last updated + Time m_firstSentTime {Seconds (0)}; //!< The send time of the packet that was most recently marked as delivered + uint32_t m_appLimited {0}; //!< The index of the last transmitted packet marked as application-limited + uint32_t m_txItemDelivered {0}; //!< The value of delivered when the acked item was sent + int32_t m_rateDelivered {0}; //!< The amount of data delivered considered to calculate delivery rate. + Time m_rateInterval {Seconds (0)}; //!< The value of interval considered to calculate delivery rate. + bool m_rateAppLimited {false}; //!< Was sample was taken when data is app limited? + }; +}; + +/** + * \brief Linux management and generation of Rate information for TCP + * + * This class is inspired by what Linux is performing in tcp_rate.c + */ +class TcpRateLinux : public TcpRateOps +{ +public: + /** + * Get the type ID. + * \brief Get the type ID. + * \return the object TypeId + */ + static TypeId GetTypeId (void); + virtual ~TcpRateLinux () override {} + + virtual void SkbSent (TcpTxItem *skb, bool isStartOfTransmission) override; + virtual void SkbDelivered (TcpTxItem * skb) override; + virtual void CalculateAppLimited (uint32_t cWnd, uint32_t in_flight, + uint32_t segmentSize, const SequenceNumber32 &tailSeq, + const SequenceNumber32 &nextTx, const uint32_t lostOut, + const uint32_t retransOut) override; + virtual const TcpRateSample & SampleGen (uint32_t delivered, uint32_t lost, + bool is_sack_reneg, uint32_t priorInFlight, + const Time &minRtt) override; + virtual const TcpRateConnection & GetConnectionRate () override { return m_rate; } + + /** + * TracedCallback signature for tcp rate update events. + * + * The callback will be fired each time the rate is updated. + * + * \param [in] rate The rate information. + */ + typedef void (* TcpRateUpdated)(const TcpRateConnection &rate); + + /** + * TracedCallback signature for tcp rate sample update events. + * + * The callback will be fired each time the rate sample is updated. + * + * \param [in] sample The rate sample that will be passed to congestion control + * algorithms. + */ + typedef void (* TcpRateSampleUpdated)(const TcpRateSample &sample); + +private: + // Rate sample related variables + TcpRateConnection m_rate; //!< Rate information + TcpRateSample m_rateSample; //!< Rate sample (continuosly updated) + + TracedCallback m_rateTrace; //!< Rate trace + TracedCallback m_rateSampleTrace; //!< Rate Sample trace +}; + +/** + * \brief Output operator. + * \param os The output stream. + * \param sample the TcpRateLinux::TcpRateSample to print. + * \returns The output stream. + */ +std::ostream & operator<< (std::ostream & os, TcpRateLinux::TcpRateSample const & sample); + +/** + * \brief Output operator. + * \param os The output stream. + * \param rate the TcpRateLinux::TcpRateConnection to print. + * \returns The output stream. + */ +std::ostream & operator<< (std::ostream & os, TcpRateLinux::TcpRateConnection const & rate); + +/** + * Comparison operator + * \param lhs left operand + * \param rhs right operand + * \return true if the operands are equal + */ +bool operator== (TcpRateLinux::TcpRateSample const & lhs, TcpRateLinux::TcpRateSample const & rhs); + +/** + * Comparison operator + * \param lhs left operand + * \param rhs right operand + * \return true if the operands are equal + */ +bool operator== (TcpRateLinux::TcpRateConnection const & lhs, TcpRateLinux::TcpRateConnection const & rhs); + +} //namespace ns3 diff --git a/src/internet/model/tcp-recovery-ops.cc b/src/internet/model/tcp-recovery-ops.cc index e0a69263fea..efb6810b9e6 100644 --- a/src/internet/model/tcp-recovery-ops.cc +++ b/src/internet/model/tcp-recovery-ops.cc @@ -89,22 +89,20 @@ TcpClassicRecovery::~TcpClassicRecovery (void) void TcpClassicRecovery::EnterRecovery (Ptr tcb, uint32_t dupAckCount, - uint32_t unAckDataCount, uint32_t lastSackedBytes) + uint32_t unAckDataCount, uint32_t deliveredBytes) { - NS_LOG_FUNCTION (this << tcb << dupAckCount << unAckDataCount << lastSackedBytes); + NS_LOG_FUNCTION (this << tcb << dupAckCount << unAckDataCount); NS_UNUSED (unAckDataCount); - NS_UNUSED (lastSackedBytes); + NS_UNUSED (deliveredBytes); tcb->m_cWnd = tcb->m_ssThresh; tcb->m_cWndInfl = tcb->m_ssThresh + (dupAckCount * tcb->m_segmentSize); } void -TcpClassicRecovery::DoRecovery (Ptr tcb, uint32_t lastAckedBytes, - uint32_t lastSackedBytes) +TcpClassicRecovery::DoRecovery (Ptr tcb, uint32_t deliveredBytes) { - NS_LOG_FUNCTION (this << tcb << lastAckedBytes << lastSackedBytes); - NS_UNUSED (lastAckedBytes); - NS_UNUSED (lastSackedBytes); + NS_LOG_FUNCTION (this << tcb << deliveredBytes); + NS_UNUSED (deliveredBytes); tcb->m_cWndInfl += tcb->m_segmentSize; } diff --git a/src/internet/model/tcp-recovery-ops.h b/src/internet/model/tcp-recovery-ops.h index 0fc26835afd..76067a246ff 100644 --- a/src/internet/model/tcp-recovery-ops.h +++ b/src/internet/model/tcp-recovery-ops.h @@ -96,10 +96,10 @@ class TcpRecoveryOps : public Object * \param tcb internal congestion state * \param dupAckCount duplicate acknowldgement count * \param unAckDataCount total bytes of data unacknowledged - * \param lastSackedBytes bytes acknowledged via SACK in the last ACK + * \param deliveredBytes bytes (S)ACKed in the last (S)ACK */ virtual void EnterRecovery (Ptr tcb, uint32_t dupAckCount, - uint32_t unAckDataCount, uint32_t lastSackedBytes) = 0; + uint32_t unAckDataCount, uint32_t deliveredBytes) = 0; /** * \brief Performs recovery based on the recovery algorithm @@ -108,15 +108,10 @@ class TcpRecoveryOps : public Object * is set to CA_RECOVERY. It performs the necessary cwnd changes * as per the recovery algorithm. * - * TODO: lastAckedBytes and lastSackedBytes should be one parameter - * that indicates how much data has been ACKed or SACKed. - * * \param tcb internal congestion state - * \param lastAckedBytes bytes acknowledged in the last ACK - * \param lastSackedBytes bytes acknowledged via SACK in the last ACK + * \param deliveredBytes bytes (S)ACKed in the last (S)ACK */ - virtual void DoRecovery (Ptr tcb, uint32_t lastAckedBytes, - uint32_t lastSackedBytes) = 0; + virtual void DoRecovery (Ptr tcb, uint32_t deliveredBytes) = 0; /** * \brief Performs cwnd adjustments at the end of recovery @@ -191,10 +186,9 @@ class TcpClassicRecovery : public TcpRecoveryOps virtual std::string GetName () const override; virtual void EnterRecovery (Ptr tcb, uint32_t dupAckCount, - uint32_t unAckDataCount, uint32_t lastSackedBytes) override; + uint32_t unAckDataCount, uint32_t deliveredBytes) override; - virtual void DoRecovery (Ptr tcb, uint32_t lastAckedBytes, - uint32_t lastSackedBytes) override; + virtual void DoRecovery (Ptr tcb, uint32_t deliveredBytes) override; virtual void ExitRecovery (Ptr tcb) override; diff --git a/src/internet/model/tcp-socket-base.cc b/src/internet/model/tcp-socket-base.cc index 23c0534d974..89b2ca734eb 100644 --- a/src/internet/model/tcp-socket-base.cc +++ b/src/internet/model/tcp-socket-base.cc @@ -58,6 +58,7 @@ #include "tcp-option-sack.h" #include "tcp-congestion-ops.h" #include "tcp-recovery-ops.h" +#include "ns3/tcp-rate-ops.h" #include #include @@ -243,6 +244,7 @@ TcpSocketBase::TcpSocketBase (void) m_rxBuffer = CreateObject (); m_txBuffer = CreateObject (); m_tcb = CreateObject (); + m_rateOps = CreateObject (); m_tcb->m_currentPacingRate = m_tcb->m_maxPacingRate; m_pacingTimer.SetFunction (&TcpSocketBase::NotifyPacingPerformed, this); @@ -370,6 +372,8 @@ TcpSocketBase::TcpSocketBase (const TcpSocketBase& sock) m_recoveryOps = sock.m_recoveryOps->Fork (); } + m_rateOps = CreateObject (); + bool ok; ok = m_tcb->TraceConnectWithoutContext ("CongestionWindow", @@ -801,6 +805,11 @@ TcpSocketBase::Send (Ptr p, uint32_t flags) m_errno = ERROR_SHUTDOWN; return -1; } + + m_rateOps->CalculateAppLimited(m_tcb->m_cWnd, m_tcb->m_bytesInFlight, m_tcb->m_segmentSize, + m_txBuffer->TailSequence (), m_tcb->m_nextTxSequence, + m_txBuffer->GetLost (), m_txBuffer->GetRetransmitsCount ()); + // Submit the data to lower layers NS_LOG_LOGIC ("txBufSize=" << m_txBuffer->Size () << " state " << TcpStateName[m_state]); if ((m_state == ESTABLISHED || m_state == CLOSE_WAIT) && AvailableWindow () > 0) @@ -1506,7 +1515,7 @@ TcpSocketBase::IsTcpOptionEnabled (uint8_t kind) const } void -TcpSocketBase::ReadOptions (const TcpHeader &tcpHeader, bool &scoreboardUpdated) +TcpSocketBase::ReadOptions (const TcpHeader &tcpHeader, uint32_t *bytesSacked) { NS_LOG_FUNCTION (this << tcpHeader); TcpHeader::TcpOptionList::const_iterator it; @@ -1520,7 +1529,7 @@ TcpSocketBase::ReadOptions (const TcpHeader &tcpHeader, bool &scoreboardUpdated) switch (option->GetKind ()) { case TcpOption::SACK: - scoreboardUpdated = ProcessOptionSack (option); + *bytesSacked = ProcessOptionSack (option); break; default: continue; @@ -1529,7 +1538,7 @@ TcpSocketBase::ReadOptions (const TcpHeader &tcpHeader, bool &scoreboardUpdated) } void -TcpSocketBase::EnterRecovery () +TcpSocketBase::EnterRecovery (uint32_t currentDelivered) { NS_LOG_FUNCTION (this); NS_ASSERT (m_tcb->m_congState != TcpSocketState::CA_RECOVERY); @@ -1567,12 +1576,16 @@ TcpSocketBase::EnterRecovery () // compatibility with old ns-3 versions uint32_t bytesInFlight = m_sackEnabled ? BytesInFlight () : BytesInFlight () + m_tcb->m_segmentSize; m_tcb->m_ssThresh = m_congestionControl->GetSsThresh (m_tcb, bytesInFlight); - m_recoveryOps->EnterRecovery (m_tcb, m_dupAckCount, UnAckDataCount (), m_txBuffer->GetSacked ()); - 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 << - " calculated in flight: " << bytesInFlight); + if (!m_congestionControl->HasCongControl ()) + { + m_recoveryOps->EnterRecovery (m_tcb, m_dupAckCount, UnAckDataCount (), currentDelivered); + 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 << + " calculated in flight: " << bytesInFlight); + } + // (4.3) Retransmit the first data segment presumed dropped DoRetransmit (); @@ -1582,7 +1595,7 @@ TcpSocketBase::EnterRecovery () } void -TcpSocketBase::DupAck () +TcpSocketBase::DupAck (uint32_t currentDelivered) { NS_LOG_FUNCTION (this); // NOTE: We do not count the DupAcks received in CA_LOSS, because we @@ -1625,9 +1638,12 @@ TcpSocketBase::DupAck () // has left the network. This is equivalent to a SACK of one block. m_txBuffer->AddRenoSack (); } - m_recoveryOps->DoRecovery (m_tcb, 0, m_txBuffer->GetSacked ()); - NS_LOG_INFO (m_dupAckCount << " Dupack received in fast recovery mode." - "Increase cwnd to " << m_tcb->m_cWnd); + if (!m_congestionControl->HasCongControl ()) + { + m_recoveryOps->DoRecovery (m_tcb, currentDelivered); + NS_LOG_INFO (m_dupAckCount << " Dupack received in fast recovery mode." + "Increase cwnd to " << m_tcb->m_cWnd); + } } else if (m_tcb->m_congState == TcpSocketState::CA_DISORDER) { @@ -1636,7 +1652,7 @@ TcpSocketBase::DupAck () // (1) If DupAcks >= DupThresh, go to step (4). if ((m_dupAckCount == m_retxThresh) && (m_highRxAckMark >= m_recover)) { - EnterRecovery (); + EnterRecovery (currentDelivered); NS_ASSERT (m_tcb->m_congState == TcpSocketState::CA_RECOVERY); } // (2) If DupAcks < DupThresh but IsLost (HighACK + 1) returns true @@ -1645,7 +1661,7 @@ TcpSocketBase::DupAck () // go to step (4). else if (m_txBuffer->IsLost (m_highRxAckMark + m_tcb->m_segmentSize)) { - EnterRecovery (); + EnterRecovery (currentDelivered); NS_ASSERT (m_tcb->m_congState == TcpSocketState::CA_RECOVERY); } else @@ -1680,12 +1696,15 @@ TcpSocketBase::ReceivedAck (Ptr packet, const TcpHeader& tcpHeader) // RFC 6675, Section 5, 1st paragraph: // Upon the receipt of any ACK containing SACK information, the // scoreboard MUST be updated via the Update () routine (done in ReadOptions) - bool scoreboardUpdated = false; - ReadOptions (tcpHeader, scoreboardUpdated); + uint32_t bytesSacked = 0; + uint64_t previousDelivered = m_rateOps->GetConnectionRate ().m_delivered; + ReadOptions (tcpHeader, &bytesSacked); SequenceNumber32 ackNumber = tcpHeader.GetAckNumber (); SequenceNumber32 oldHeadSequence = m_txBuffer->HeadSequence (); - m_txBuffer->DiscardUpTo (ackNumber); + m_txBuffer->DiscardUpTo (ackNumber, MakeCallback (&TcpRateOps::SkbDelivered, m_rateOps)); + + uint32_t currentDelivered = static_cast (m_rateOps->GetConnectionRate ().m_delivered - previousDelivered); if (ackNumber > oldHeadSequence && (m_tcb->m_ecnState != TcpSocketState::ECN_DISABLED) && (tcpHeader.GetFlags () & TcpHeader::ECE)) { @@ -1700,7 +1719,21 @@ TcpSocketBase::ReceivedAck (Ptr packet, const TcpHeader& tcpHeader) // RFC 6675 Section 5: 2nd, 3rd paragraph and point (A), (B) implementation // are inside the function ProcessAck - ProcessAck (ackNumber, scoreboardUpdated, oldHeadSequence); + ProcessAck (ackNumber, (bytesSacked > 0), currentDelivered, oldHeadSequence); + + if (m_congestionControl->HasCongControl ()) + { + uint32_t previousLost = m_txBuffer->GetLost (); + uint32_t priorInFlight = m_tcb->m_bytesInFlight.Get (); + uint32_t currentLost = m_txBuffer->GetLost (); + uint32_t lost = (currentLost > previousLost) ? + currentLost - previousLost : + previousLost - currentLost; + auto rateSample = m_rateOps->SampleGen (currentDelivered, lost, + false, priorInFlight, m_tcb->m_minRtt); + auto rateConn = m_rateOps->GetConnectionRate (); + m_congestionControl->CongControl(m_tcb, rateConn, rateSample); + } // If there is any data piggybacked, store it into m_rxBuffer if (packet->GetSize () > 0) @@ -1714,8 +1747,8 @@ TcpSocketBase::ReceivedAck (Ptr packet, const TcpHeader& tcpHeader) } void -TcpSocketBase::ProcessAck (const SequenceNumber32 &ackNumber, bool scoreboardUpdated, - const SequenceNumber32 &oldHeadSequence) +TcpSocketBase::ProcessAck(const SequenceNumber32 &ackNumber, bool scoreboardUpdated, + uint32_t currentDelivered, const SequenceNumber32 &oldHeadSequence) { NS_LOG_FUNCTION (this << ackNumber << scoreboardUpdated); // RFC 6675, Section 5, 2nd paragraph: @@ -1724,6 +1757,7 @@ TcpSocketBase::ProcessAck (const SequenceNumber32 &ackNumber, bool scoreboardUpd bool exitedFastRecovery = false; uint32_t oldDupAckCount = m_dupAckCount; // remember the old value m_tcb->m_lastAckedSeq = ackNumber; // Update lastAckedSeq + uint32_t bytesAcked = 0; /* In RFC 5681 the definition of duplicate acknowledgment was strict: * @@ -1761,7 +1795,7 @@ TcpSocketBase::ProcessAck (const SequenceNumber32 &ackNumber, bool scoreboardUpd { // loss recovery check is done inside this function thanks to // the congestion state machine - DupAck (); + DupAck (currentDelivered); } if (ackNumber == oldHeadSequence @@ -1787,13 +1821,15 @@ TcpSocketBase::ProcessAck (const SequenceNumber32 &ackNumber, bool scoreboardUpd { // Please remember that, with SACK, we can enter here even if we // received a dupack. - uint32_t bytesAcked = ackNumber - oldHeadSequence; + bytesAcked = ackNumber - oldHeadSequence; uint32_t segsAcked = bytesAcked / m_tcb->m_segmentSize; m_bytesAckedNotProcessed += bytesAcked % m_tcb->m_segmentSize; + bytesAcked -= bytesAcked % m_tcb->m_segmentSize; if (m_bytesAckedNotProcessed >= m_tcb->m_segmentSize) { segsAcked += 1; + bytesAcked += m_tcb->m_segmentSize; m_bytesAckedNotProcessed -= m_tcb->m_segmentSize; } @@ -1837,9 +1873,9 @@ TcpSocketBase::ProcessAck (const SequenceNumber32 &ackNumber, bool scoreboardUpd } DoRetransmit (); // Assume the next seq is lost. Retransmit lost packet m_tcb->m_cWndInfl = SafeSubtraction (m_tcb->m_cWndInfl, bytesAcked); - if (segsAcked >= 1) + if (!m_congestionControl->HasCongControl () && segsAcked >= 1) { - m_recoveryOps->DoRecovery (m_tcb, bytesAcked, m_txBuffer->GetSacked ()); + m_recoveryOps->DoRecovery (m_tcb, currentDelivered); } // This partial ACK acknowledge the fact that one segment has been @@ -2834,13 +2870,13 @@ TcpSocketBase::SendDataPacket (SequenceNumber32 seq, uint32_t maxSize, bool with { NS_LOG_FUNCTION (this << seq << maxSize << withAck); - bool isRetransmission = false; - if (seq != m_tcb->m_highTxMark) - { - isRetransmission = true; - } + bool isStartOfTransmission = BytesInFlight () == 0U; + TcpTxItem *outItem = m_txBuffer->CopyFromSequence (maxSize, seq); - Ptr p = m_txBuffer->CopyFromSequence (maxSize, seq); + m_rateOps->SkbSent(outItem, isStartOfTransmission); + + bool isRetransmission = outItem->IsRetrans (); + Ptr p = outItem->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)); @@ -2957,7 +2993,7 @@ TcpSocketBase::SendDataPacket (SequenceNumber32 seq, uint32_t maxSize, bool with } // Notify the application of the data being sent unless this is a retransmit - if (seq + sz > m_tcb->m_highTxMark) + if (!isRetransmission) { Simulator::ScheduleNow (&TcpSocketBase::NotifyDataSent, this, (seq + sz - m_tcb->m_highTxMark.Get ())); @@ -3525,11 +3561,11 @@ TcpSocketBase::ReTxTimeout () } // Cwnd set to 1 MSS - m_tcb->m_cWnd = m_tcb->m_segmentSize; - m_tcb->m_cWndInfl = m_tcb->m_cWnd; m_congestionControl->CwndEvent (m_tcb, TcpSocketState::CA_EVENT_LOSS); m_congestionControl->CongestionStateSet (m_tcb, TcpSocketState::CA_LOSS); m_tcb->m_congState = TcpSocketState::CA_LOSS; + m_tcb->m_cWnd = m_tcb->m_segmentSize; + m_tcb->m_cWndInfl = m_tcb->m_cWnd; m_pacingTimer.Cancel (); @@ -3588,7 +3624,7 @@ TcpSocketBase::PersistTimeout () { NS_LOG_LOGIC ("PersistTimeout expired at " << Simulator::Now ().GetSeconds ()); m_persistTimeout = std::min (Seconds (60), Time (2 * m_persistTimeout)); // max persist timeout = 60s - Ptr p = m_txBuffer->CopyFromSequence (1, m_tcb->m_nextTxSequence); + Ptr p = m_txBuffer->CopyFromSequence (1, m_tcb->m_nextTxSequence)->GetPacketCopy (); m_txBuffer->ResetLastSegmentSent (); TcpHeader tcpHeader; tcpHeader.SetSequenceNumber (m_tcb->m_nextTxSequence); @@ -3939,14 +3975,13 @@ TcpSocketBase::AddOptionWScale (TcpHeader &header) static_cast (m_rcvWindShift)); } -bool +uint32_t TcpSocketBase::ProcessOptionSack (const Ptr option) { NS_LOG_FUNCTION (this << option); Ptr s = DynamicCast (option); - TcpOptionSack::SackList list = s->GetSackList (); - return m_txBuffer->Update (list); + return m_txBuffer->Update (s->GetSackList (), MakeCallback (&TcpRateOps::SkbDelivered, m_rateOps)); } void diff --git a/src/internet/model/tcp-socket-base.h b/src/internet/model/tcp-socket-base.h index 1412e606891..764037b8b13 100644 --- a/src/internet/model/tcp-socket-base.h +++ b/src/internet/model/tcp-socket-base.h @@ -49,6 +49,7 @@ class TcpTxBuffer; class TcpOption; class Ipv4Interface; class Ipv6Interface; +class TcpRateOps; /** * \ingroup tcp @@ -965,9 +966,11 @@ class TcpSocketBase : public TcpSocket * \param scoreboardUpdated if true indicates that the scoreboard has been * \param oldHeadSequence value of HeadSequence before ack * updated with SACK information + * \param currentDelivered The number of bytes (S)ACKed + * \return the number of bytes (newly) acked, or 0 if it was a dupack */ virtual void ProcessAck (const SequenceNumber32 &ackNumber, bool scoreboardUpdated, - const SequenceNumber32 &oldHeadSequence); + uint32_t currentDelivered, const SequenceNumber32 &oldHeadSequence); /** * \brief Recv of a data, put into buffer, call L7 to get it if necessary @@ -1002,13 +1005,17 @@ class TcpSocketBase : public TcpSocket /** * \brief Dupack management + * + * \param currentDelivered Current (S)ACKed bytes */ - void DupAck (); + void DupAck (uint32_t currentDelivered); /** * \brief Enter the CA_RECOVERY, and retransmit the head + * + * \param currentDelivered Currently (S)ACKed bytes */ - void EnterRecovery (); + void EnterRecovery (uint32_t currentDelivered); /** * \brief An RTO event happened @@ -1051,10 +1058,9 @@ class TcpSocketBase : public TcpSocket * Timestamp and Window scale are managed in other pieces of code. * * \param tcpHeader Header of the segment - * \param scoreboardUpdated indicates if the scoreboard was updated due to a - * SACK option + * \param [in] bytesSacked Number of bytes SACKed, or 0 */ - void ReadOptions (const TcpHeader &tcpHeader, bool &scoreboardUpdated); + void ReadOptions (const TcpHeader &tcpHeader, uint32_t *bytesSacked); /** * \brief Return true if the specified option is enabled @@ -1106,9 +1112,9 @@ class TcpSocketBase : public TcpSocket * \brief Read the SACK option * * \param option SACK option from the header - * \returns true in case of an update to the SACKed blocks + * \returns the number of bytes sacked by this option */ - bool ProcessOptionSack (const Ptr option); + uint32_t ProcessOptionSack(const Ptr option); /** * \brief Add the SACK PERMITTED option to the header @@ -1253,6 +1259,7 @@ class TcpSocketBase : public TcpSocket Ptr m_tcb; //!< Congestion control information Ptr m_congestionControl; //!< Congestion control Ptr m_recoveryOps; //!< Recovery Algorithm + Ptr m_rateOps; //!< Rate operations // Guesses over the other connection end bool m_isFirstPartialAck {true}; //!< First partial ACK during RECOVERY diff --git a/src/internet/model/tcp-socket-state.h b/src/internet/model/tcp-socket-state.h index a41958820b4..c15816eee95 100644 --- a/src/internet/model/tcp-socket-state.h +++ b/src/internet/model/tcp-socket-state.h @@ -173,6 +173,20 @@ class TcpSocketState : public Object { return m_ssThresh / m_segmentSize; } + + /** + * \brief Get the initial sequence number of the connection + * + * The returned value is, right now, the value used in the inizialization + * of the m_nextTxSequence variable. It has no effect on the value 'on the wire'. + * \return the initial sequence number + */ + SequenceNumber32 GetInitialTxSeq () const + { + // To see a different value "on the wire", it is necessary to add + // a "SetInitialTxSequence" and its logic here and in TcpSocketBase. + return SequenceNumber32 (0); + } }; namespace TracedValueCallback { diff --git a/src/internet/model/tcp-tx-buffer.cc b/src/internet/model/tcp-tx-buffer.cc index bb1c17e2482..f36acd1be2b 100644 --- a/src/internet/model/tcp-tx-buffer.cc +++ b/src/internet/model/tcp-tx-buffer.cc @@ -32,47 +32,9 @@ namespace ns3 { NS_LOG_COMPONENT_DEFINE ("TcpTxBuffer"); - -void -TcpTxItem::Print (std::ostream &os) const -{ - bool comma = false; - os << "[" << m_startSeq << ";" << m_startSeq + GetSeqSize () << "|" - << GetSeqSize () << "]"; - - if (m_lost) - { - os << "[lost]"; - comma = true; - } - if (m_retrans) - { - if (comma) - { - os << ","; - } - - os << "[retrans]"; - comma = true; - } - if (m_sacked) - { - if (comma) - { - os << ","; - } - os << "[sacked]"; - comma = true; - } - if (comma) - { - os << ","; - } - os << "[" << m_lastSent.GetSeconds () << "]"; -} - NS_OBJECT_ENSURE_REGISTERED (TcpTxBuffer); +Callback TcpTxBuffer::m_nullCb = MakeNullCallback (); TypeId TcpTxBuffer::GetTypeId (void) { @@ -212,7 +174,7 @@ TcpTxBuffer::SizeFromSequence (const SequenceNumber32& seq) const return 0; } -Ptr +TcpTxItem * TcpTxBuffer::CopyFromSequence (uint32_t numBytes, const SequenceNumber32& seq) { NS_LOG_FUNCTION (this << numBytes << seq); @@ -226,7 +188,7 @@ TcpTxBuffer::CopyFromSequence (uint32_t numBytes, const SequenceNumber32& seq) if (s == 0) { - return Create (); + return nullptr; } TcpTxItem *outItem = nullptr; @@ -265,14 +227,11 @@ TcpTxBuffer::CopyFromSequence (uint32_t numBytes, const SequenceNumber32& seq) } outItem->m_lastSent = Simulator::Now (); - Ptr toRet = outItem->m_packet->Copy (); - - NS_ASSERT (toRet->GetSize () <= s); NS_ASSERT_MSG (outItem->m_startSeq >= m_firstByteSeq, "Returning an item " << *outItem << " with SND.UNA as " << m_firstByteSeq); ConsistencyCheck (); - return toRet; + return outItem; } TcpTxItem* @@ -599,7 +558,7 @@ TcpTxBuffer::MergeItems (TcpTxItem *t1, TcpTxItem *t2) const // If one is retrans and the other is not, cancel the retransmitted flag. // We are merging this segment for the retransmit, so the count will - // be updated in GetTransmittedSegment. + // be updated in MarkTransmittedSegment. if (! AreEquals (t1->m_retrans, t2->m_retrans)) { if (t1->m_retrans) @@ -649,7 +608,8 @@ TcpTxBuffer::RemoveFromCounts (TcpTxItem *item, uint32_t size) } } void -TcpTxBuffer::DiscardUpTo (const SequenceNumber32& seq) +TcpTxBuffer::DiscardUpTo (const SequenceNumber32& seq, + const Callback &beforeDelCb) { NS_LOG_FUNCTION (this << seq); @@ -671,7 +631,7 @@ TcpTxBuffer::DiscardUpTo (const SequenceNumber32& seq) if (i == m_sentList.end ()) { // Move data from app list to sent list, so we can delete the item - Ptr p = CopyFromSequence (offset, m_firstByteSeq); + Ptr p = CopyFromSequence (offset, m_firstByteSeq)->GetPacketCopy (); NS_ASSERT (p != nullptr); NS_UNUSED (p); i = m_sentList.begin (); @@ -697,6 +657,13 @@ TcpTxBuffer::DiscardUpTo (const SequenceNumber32& seq) NS_LOG_INFO ("Removed " << *item << " lost: " << m_lostOut << " retrans: " << m_retrans << " sacked: " << m_sackedOut << ". Remaining data " << m_size); + + if (!beforeDelCb.IsNull ()) + { + // Inform Rate algorithms only when a full packet is ACKed + beforeDelCb (item); + } + delete item; } else if (offset > 0) @@ -758,23 +725,24 @@ TcpTxBuffer::DiscardUpTo (const SequenceNumber32& seq) ConsistencyCheck (); } -bool -TcpTxBuffer::Update (const TcpOptionSack::SackList &list) +uint32_t +TcpTxBuffer::Update (const TcpOptionSack::SackList &list, + const Callback &sackedCb) { NS_LOG_FUNCTION (this); NS_LOG_INFO ("Updating scoreboard, got " << list.size () << " blocks to analyze"); - bool modified = false; + uint32_t bytesSacked = 0; for (auto option_it = list.begin (); option_it != list.end (); ++option_it) { PacketList::iterator item_it = m_sentList.begin (); SequenceNumber32 beginOfCurrentPacket = m_firstByteSeq; - if (m_firstByteSeq + m_sentSize < (*option_it).first && !modified) + if (m_firstByteSeq + m_sentSize < (*option_it).first) { NS_LOG_INFO ("Not updating scoreboard, the option block is outside the sent list"); - return false; + return bytesSacked; } while (item_it != m_sentList.end ()) @@ -806,6 +774,7 @@ TcpTxBuffer::Update (const TcpOptionSack::SackList &list) (*item_it)->m_sacked = true; m_sackedOut += (*item_it)->m_packet->GetSize (); + bytesSacked += (*item_it)->m_packet->GetSize (); if (m_highestSack.first == m_sentList.end() || m_highestSack.second <= beginOfCurrentPacket + pktSize) @@ -817,8 +786,12 @@ TcpTxBuffer::Update (const TcpOptionSack::SackList &list) ", checking sentList for block " << *(*item_it) << ", found in the sackboard, sacking, current highSack: " << m_highestSack.second); + + if (!sackedCb.IsNull ()) + { + sackedCb (*item_it); + } } - modified = true; } else if (beginOfCurrentPacket + pktSize > (*option_it).second) { @@ -834,9 +807,9 @@ TcpTxBuffer::Update (const TcpOptionSack::SackList &list) } } - if (modified) + if (bytesSacked > 0) { - NS_ASSERT_MSG (modified && m_highestSack.first != m_sentList.end(), "Buffer status: " << *this); + NS_ASSERT_MSG (m_highestSack.first != m_sentList.end(), "Buffer status: " << *this); UpdateLostCount (); } @@ -845,7 +818,7 @@ TcpTxBuffer::Update (const TcpOptionSack::SackList &list) //NS_ASSERT (list.size () == 0 || modified); // Assert for duplicated SACK or // impossiblity to map the option into the sent blocks ConsistencyCheck (); - return modified; + return bytesSacked; } void @@ -1423,10 +1396,10 @@ operator<< (std::ostream & os, TcpTxBuffer const & tcpTxBuf) SequenceNumber32 beginOfCurrentPacket = tcpTxBuf.m_firstByteSeq; uint32_t sentSize = 0, appSize = 0; - Ptr p; + Ptr p; for (it = tcpTxBuf.m_sentList.begin (); it != tcpTxBuf.m_sentList.end (); ++it) { - p = (*it)->m_packet; + p = (*it)->GetPacket (); ss << "{"; (*it)->Print (ss); ss << "}"; @@ -1436,7 +1409,7 @@ operator<< (std::ostream & os, TcpTxBuffer const & tcpTxBuf) for (it = tcpTxBuf.m_appList.begin (); it != tcpTxBuf.m_appList.end (); ++it) { - appSize += (*it)->m_packet->GetSize (); + appSize += (*it)->GetPacket ()->GetSize (); } os << "Sent list: " << ss.str () << ", size = " << tcpTxBuf.m_sentList.size () << diff --git a/src/internet/model/tcp-tx-buffer.h b/src/internet/model/tcp-tx-buffer.h index ca8cdd94595..15125755897 100644 --- a/src/internet/model/tcp-tx-buffer.h +++ b/src/internet/model/tcp-tx-buffer.h @@ -25,44 +25,12 @@ #include "ns3/object.h" #include "ns3/traced-value.h" #include "ns3/sequence-number.h" -#include "ns3/nstime.h" #include "ns3/tcp-option-sack.h" -#include "ns3/packet.h" +#include "ns3/tcp-tx-item.h" namespace ns3 { class Packet; -/** - * \ingroup tcp - * - * \brief Item that encloses the application packet and some flags for it - */ -class TcpTxItem -{ -public: - // Default constructor, copy-constructor, destructor - - /** - * \brief Print the time - * \param os ostream - */ - void Print (std::ostream &os) const; - - /** - * \brief Get the size in the sequence number space - * - * \return 1 if the packet size is 0 or there's no packet, otherwise the size of the packet - */ - uint32_t GetSeqSize (void) const { return m_packet && m_packet->GetSize () > 0 ? m_packet->GetSize () : 1; } - - SequenceNumber32 m_startSeq {0}; //!< Sequence number of the item (if transmitted) - Ptr m_packet {nullptr}; //!< Application packet (can be null) - bool m_lost {false}; //!< Indicates if the segment has been lost (RTO) - bool m_retrans {false}; //!< Indicates if the segment is retransmitted - Time m_lastSent {Time::Min()};//!< Timestamp of the time at which the segment has been sent last time - bool m_sacked {false}; //!< Indicates if the segment has been SACKed -}; - /** * \ingroup tcp * @@ -274,9 +242,10 @@ class TcpTxBuffer : public Object * * \param numBytes number of bytes to copy * \param seq start sequence number to extract - * \returns a packet + * \returns a pointer to the TcpTxItem that corresponds to what requested. + * Please do not delete the pointer, nor modify Packet data or sequence numbers. */ - Ptr CopyFromSequence (uint32_t numBytes, const SequenceNumber32& seq); + TcpTxItem* CopyFromSequence (uint32_t numBytes, const SequenceNumber32& seq); /** * \brief Set the head sequence of the buffer @@ -292,15 +261,21 @@ class TcpTxBuffer : public Object * * \param seq The first sequence number to maintain after discarding all the * previous sequences. + * \param beforeDelCb Callback invoked, if it is not null, before the deletion + * of an Item (because it was, probably, ACKed) */ - void DiscardUpTo (const SequenceNumber32& seq); + void DiscardUpTo (const SequenceNumber32& seq, + const Callback &beforeDelCb = m_nullCb); /** * \brief Update the scoreboard * \param list list of SACKed blocks - * \returns true in case of an update + * \param sackedCb Callback invoked, if it is not null, when a segment has been + * SACKed by the receiver. + * \returns the number of bytes newly sacked by the list of blocks */ - bool Update (const TcpOptionSack::SackList &list); + uint32_t Update (const TcpOptionSack::SackList &list, + const Callback &sackedCb = m_nullCb); /** * \brief Check if a segment is lost @@ -616,6 +591,7 @@ class TcpTxBuffer : public Object uint32_t m_segmentSize {0}; //!< Segment size from TcpSocketBase bool m_renoSack {false}; //!< Indicates if AddRenoSack was called + static Callback m_nullCb; //!< Null callback for an item }; /** diff --git a/src/internet/model/tcp-tx-item.cc b/src/internet/model/tcp-tx-item.cc new file mode 100644 index 00000000000..e556b846b73 --- /dev/null +++ b/src/internet/model/tcp-tx-item.cc @@ -0,0 +1,62 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2018 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 + * + */ +#include "tcp-tx-item.h" + +namespace ns3 { + +void +TcpTxItem::Print (std::ostream &os) const +{ + bool comma = false; + os << "[" << m_startSeq << ";" << m_startSeq + GetSeqSize () << "|" + << GetSeqSize () << "]"; + + if (m_lost) + { + os << "[lost]"; + comma = true; + } + if (m_retrans) + { + if (comma) + { + os << ","; + } + + os << "[retrans]"; + comma = true; + } + if (m_sacked) + { + if (comma) + { + os << ","; + } + os << "[sacked]"; + comma = true; + } + if (comma) + { + os << ","; + } + os << "[" << m_lastSent.GetSeconds () << "]"; +} + + +} // namespace ns3 diff --git a/src/internet/model/tcp-tx-item.h b/src/internet/model/tcp-tx-item.h new file mode 100644 index 00000000000..4d21ff57694 --- /dev/null +++ b/src/internet/model/tcp-tx-item.h @@ -0,0 +1,115 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2018 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 + * + */ +#pragma once + +#include "ns3/packet.h" +#include "ns3/nstime.h" +#include "ns3/sequence-number.h" + +namespace ns3 { +/** + * \ingroup tcp + * + * \brief Item that encloses the application packet and some flags for it + */ +class TcpTxItem +{ +public: + // Default constructor, copy-constructor, destructor + + /** + * \brief Print the time + * \param os ostream + */ + void Print (std::ostream &os) const; + + /** + * \brief Get the size in the sequence number space + * + * \return 1 if the packet size is 0 or there's no packet, otherwise the size of the packet + */ + uint32_t GetSeqSize (void) const { return m_packet && m_packet->GetSize () > 0 ? m_packet->GetSize () : 1; } + + /** + * \brief Is the item sacked? + * \return true if the item is sacked, false otherwise + */ + bool IsSacked (void) const { return m_sacked; } + + /** + * \brief Is the item retransmitted? + * \return true if the item have been retransmitted + */ + bool IsRetrans (void) const { return m_retrans; } + + /** + * \brief Get a copy of the Packet underlying this item + * \return a copy of the Packet + */ + Ptr GetPacketCopy (void) const { return m_packet->Copy (); } + + /** + * \brief Get the Packet underlying this item + * \return a pointer to a const Packet + */ + Ptr GetPacket (void) const { return m_packet; } + + /** + * \brief Get a reference to the time the packet was sent for the last time + * \return a reference to the last sent time + */ + const Time & GetLastSent (void) const { return m_lastSent; } + + /** + * \brief Various rate-related information, can be accessed by TcpRateOps. + * + * Note: This is not enforced through C++, but you developer must honour + * this description. + */ + struct RateInformation + { + uint64_t m_delivered {0}; //!< Connection's delivered data at the time the packet was sent + Time m_deliveredTime {Time::Max ()};//!< Connection's delivered time at the time the packet was sent + Time m_firstSent {Time::Max ()};//!< Connection's first sent time at the time the packet was sent + bool m_isAppLimited {false}; //!< Connection's app limited at the time the packet was sent + }; + + /** + * \brief Get (to modify) the Rate Information of this item + * + * \return A reference to the rate information. + */ + RateInformation & GetRateInformation (void) { return m_rateInfo; } + +private: + // Only TcpTxBuffer is allower to touch this part of the TcpTxItem, to manage + // its internal lists and counters + friend class TcpTxBuffer; + + SequenceNumber32 m_startSeq {0}; //!< Sequence number of the item (if transmitted) + Ptr m_packet {nullptr}; //!< Application packet (can be null) + bool m_lost {false}; //!< Indicates if the segment has been lost (RTO) + bool m_retrans {false}; //!< Indicates if the segment is retransmitted + Time m_lastSent {Time::Max ()};//!< Timestamp of the time at which the segment has been sent last time + bool m_sacked {false}; //!< Indicates if the segment has been SACKed + + RateInformation m_rateInfo; //!< Rate information of the item +}; + +} //namespace ns3 diff --git a/src/internet/test/tcp-classic-recovery-test.cc b/src/internet/test/tcp-classic-recovery-test.cc index a0ec1914e1d..c0e9ee964d1 100644 --- a/src/internet/test/tcp-classic-recovery-test.cc +++ b/src/internet/test/tcp-classic-recovery-test.cc @@ -98,7 +98,7 @@ ClassicRecoveryTest::DoRun () uint32_t cWndInflPrevious = m_state->m_cWndInfl; uint32_t cWndPrevious = m_state->m_cWnd; - recovery->DoRecovery (m_state, 0, 500); + recovery->DoRecovery (m_state, 500); NS_TEST_ASSERT_MSG_EQ (m_state->m_cWndInfl, (cWndInflPrevious + m_state->m_segmentSize), "m_cWndInfl should be incresed by one segmentSize on calling DoRecovery"); NS_TEST_ASSERT_MSG_EQ (m_state->m_cWnd, cWndPrevious, @@ -122,9 +122,6 @@ class ClassicRecoveryTestSuite : public TestSuite public: ClassicRecoveryTestSuite () : TestSuite ("tcp-classic-recovery-test", UNIT) { - AddTestCase (new ClassicRecoveryTest (3000, 500, 2500, 3, - "Classic recovery test on cWnd and cWndInfl with 500 bytes segmentSize"), - TestCase::QUICK); AddTestCase (new ClassicRecoveryTest (3000, 500, 2500, 3, "Classic recovery test with 500 bytes segmentSize"), TestCase::QUICK); AddTestCase (new ClassicRecoveryTest (3000, 1000, 2500, 3, "Classic recovery test with 1000 bytes segmentSize"), diff --git a/src/internet/test/tcp-ecn-test.cc b/src/internet/test/tcp-ecn-test.cc index 23ed79f0454..1f0d15227c7 100644 --- a/src/internet/test/tcp-ecn-test.cc +++ b/src/internet/test/tcp-ecn-test.cc @@ -161,7 +161,7 @@ TcpSocketCongestedRouter::SendDataPacket (SequenceNumber32 seq, uint32_t maxSize isRetransmission = true; } - Ptr p = m_txBuffer->CopyFromSequence (maxSize, seq); + Ptr 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)); diff --git a/src/internet/test/tcp-general-test.cc b/src/internet/test/tcp-general-test.cc index 03ceaac6884..605cad534b9 100644 --- a/src/internet/test/tcp-general-test.cc +++ b/src/internet/test/tcp-general-test.cc @@ -224,6 +224,10 @@ TcpGeneralTest::DoRun (void) MakeCallback (&TcpGeneralTest::NextTxSeqTrace, this)); m_senderSocket->TraceConnectWithoutContext ("HighestSequence", MakeCallback (&TcpGeneralTest::HighestTxSeqTrace, this)); + m_senderSocket->m_rateOps->TraceConnectWithoutContext ("TcpRateUpdated", + MakeCallback (&TcpGeneralTest::RateUpdatedTrace, this)); + m_senderSocket->m_rateOps->TraceConnectWithoutContext ("TcpRateSampleUpdated", + MakeCallback (&TcpGeneralTest::RateSampleUpdatedTrace, this)); m_remoteAddr = InetSocketAddress (serverAddress, 4477); diff --git a/src/internet/test/tcp-general-test.h b/src/internet/test/tcp-general-test.h index d2100e0d704..a1448fa3f26 100644 --- a/src/internet/test/tcp-general-test.h +++ b/src/internet/test/tcp-general-test.h @@ -24,6 +24,7 @@ #include "ns3/tcp-socket-base.h" #include "ns3/tcp-congestion-ops.h" #include "ns3/tcp-recovery-ops.h" +#include "ns3/tcp-rate-ops.h" #include "ns3/test.h" namespace ns3 { @@ -778,6 +779,24 @@ class TcpGeneralTest : public TestCase NS_UNUSED (newValue); } + /** + * \brief Track the rate value of TcpRateLinux. + * \param rate updated value of TcpRate. + */ + virtual void RateUpdatedTrace (const TcpRateLinux::TcpRateConnection &rate) + { + NS_UNUSED (rate); + } + + /** + * \brief Track the rate sample value of TcpRateLinux. + * \param sample updated value of TcpRateSample. + */ + virtual void RateSampleUpdatedTrace (const TcpRateLinux::TcpRateSample &sample) + { + NS_UNUSED (sample); + } + /** * \brief Socket closed normally * \param who the socket closed (SENDER or RECEIVER) diff --git a/src/internet/test/tcp-prr-recovery-test.cc b/src/internet/test/tcp-prr-recovery-test.cc index aee46aa1949..25a97a359ab 100644 --- a/src/internet/test/tcp-prr-recovery-test.cc +++ b/src/internet/test/tcp-prr-recovery-test.cc @@ -46,7 +46,7 @@ class PrrRecoveryTest : public TestCase * \param ssThresh Slow Start Threshold. * \param unAckDataCount Unacknowledged data at the start of recovery. * \param bytesInFlight Current bytes in flight. - * \para m_lastSackedBytes Bytes SACKed on last acknowledgment. + * \para m_deliveredBytes Bytes SACKed on last acknowledgment. * \para bytesSent Bytes sent while in recovery phase. * \para reductionBound Type of reduction bound to be used. * \param name Test description. @@ -56,7 +56,7 @@ class PrrRecoveryTest : public TestCase uint32_t ssThresh, uint32_t unAckDataCount, uint32_t bytesInFlight, - uint32_t m_lastSackedBytes, + uint32_t m_deliveredBytes, uint32_t bytesSent, const std::string &reductionBound, const std::string &name); @@ -69,7 +69,7 @@ class PrrRecoveryTest : public TestCase uint32_t m_ssThresh; //!< Slow Start Threshold. uint32_t m_unAckDataCount; //!< Unacknowledged data at the start of recovery. uint32_t m_bytesInFlight; //!< Current bytes in flight. - uint32_t m_lastSackedBytes; //!< Bytes SACKed on last acknowledgment. + uint32_t m_deliveredBytes; //!< Bytes SACKed on last acknowledgment. uint32_t m_bytesSent; //!< Bytes sent while in recovery phase. const std::string m_reductionBound; //!< Type of reduction bound to be used. @@ -81,7 +81,7 @@ PrrRecoveryTest::PrrRecoveryTest (uint32_t cWnd, uint32_t ssThresh, uint32_t unAckDataCount, uint32_t bytesInFlight, - uint32_t lastSackedBytes, + uint32_t deliveredBytes, uint32_t bytesSent, const std::string &reductionBound, const std::string &name) @@ -91,7 +91,7 @@ PrrRecoveryTest::PrrRecoveryTest (uint32_t cWnd, m_ssThresh (ssThresh), m_unAckDataCount (unAckDataCount), m_bytesInFlight (bytesInFlight), - m_lastSackedBytes (lastSackedBytes), + m_deliveredBytes (deliveredBytes), m_bytesSent (bytesSent), m_reductionBound (reductionBound) { @@ -125,7 +125,7 @@ PrrRecoveryTest::DoRun () m_bytesInFlight += m_state->m_cWnd.Get () - m_cWnd; m_state->m_bytesInFlight = m_bytesInFlight; m_cWnd = m_state->m_cWnd.Get (); - recovery->DoRecovery (m_state, 0, m_lastSackedBytes); + recovery->DoRecovery (m_state, m_deliveredBytes); if (m_bytesInFlight > m_state->m_ssThresh) { diff --git a/src/internet/test/tcp-rate-ops-test.cc b/src/internet/test/tcp-rate-ops-test.cc new file mode 100644 index 00000000000..c281d4d868c --- /dev/null +++ b/src/internet/test/tcp-rate-ops-test.cc @@ -0,0 +1,579 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2018 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 + * + * Authors: Vivek Jain + * Viyom Mittal + * Mohit P. Tahiliani + */ + +#include "ns3/test.h" +#include "ns3/tcp-tx-item.h" +#include "ns3/log.h" +#include "ns3/tcp-rate-ops.h" +#include "tcp-general-test.h" +#include "ns3/config.h" +#include "tcp-error-model.h" +#include "ns3/tcp-tx-buffer.h" + +using namespace ns3; + +NS_LOG_COMPONENT_DEFINE ("TcpRateOpsTestSuite"); + +class MimicCongControl; + +/** + * \ingroup internet-tests + * \ingroup tests + * + * \brief The TcpRateLinux Basic Test + */ +class TcpRateLinuxBasicTest : public TestCase +{ +public: + TcpRateLinuxBasicTest (uint32_t cWnd, SequenceNumber32 tailSeq, SequenceNumber32 nextTx, + uint32_t lostOut, uint32_t retransOut, uint32_t testCase, + std::string testName); + +private: + virtual void DoRun (void); + + void SendSkb (TcpTxItem * skb); + void SkbDelivered (TcpTxItem * skb); + + TcpRateLinux m_rateOps; + uint32_t m_cWnd; + uint32_t m_inFlight; + uint32_t m_segmentSize; + uint32_t m_delivered; + Time m_deliveredTime; + SequenceNumber32 m_tailSeq; + SequenceNumber32 m_nextTx; + uint32_t m_lostOut; + uint32_t m_retransOut; + uint32_t m_testCase; + std::vector m_skbs; +}; + +TcpRateLinuxBasicTest::TcpRateLinuxBasicTest (uint32_t cWnd, SequenceNumber32 tailSeq, + SequenceNumber32 nextTx, uint32_t lostOut, + uint32_t retransOut, uint32_t testCase, std::string testName) + : TestCase (testName), + m_cWnd (cWnd), + m_inFlight (0), + m_segmentSize (1), + m_delivered (0), + m_deliveredTime (Seconds (0)), + m_tailSeq (tailSeq), + m_nextTx (nextTx), + m_lostOut (lostOut), + m_retransOut (retransOut), + m_testCase (testCase) +{ +} + +void +TcpRateLinuxBasicTest::DoRun () +{ + for (uint8_t i = 0; i < 100; ++i) + { + m_skbs.push_back (new TcpTxItem ()); + Simulator::Schedule (Time (Seconds (i * 0.01)), &TcpRateLinuxBasicTest::SendSkb, this, m_skbs [i]); + } + + for (uint8_t i = 0; i < 100; ++i) + { + Simulator::Schedule (Time (Seconds ((i + 1) * 0.1)), &TcpRateLinuxBasicTest::SkbDelivered, this, m_skbs [i]); + } + + Simulator::Run (); + Simulator::Destroy (); + + for (uint8_t i = 0; i < 100; ++i) + { + delete m_skbs[i]; + } +} + +void +TcpRateLinuxBasicTest::SendSkb (TcpTxItem * skb) +{ + bool isStartOfTransmission = m_inFlight == 0; + m_rateOps.CalculateAppLimited (m_cWnd, m_inFlight, m_segmentSize, m_tailSeq, m_nextTx, 0, 0); + m_rateOps.SkbSent (skb, isStartOfTransmission); + m_inFlight += skb->GetSeqSize (); + + NS_TEST_ASSERT_MSG_EQ (skb->GetRateInformation ().m_delivered, m_delivered, "SKB should have delivered equal to current value of total delivered"); + + if (isStartOfTransmission) + { + NS_TEST_ASSERT_MSG_EQ (skb->GetRateInformation ().m_deliveredTime, Simulator::Now (), "SKB should have updated the delivered time to current value"); + } + else + { + NS_TEST_ASSERT_MSG_EQ (skb->GetRateInformation ().m_deliveredTime, m_deliveredTime, "SKB should have updated the delivered time to current value"); + } +} + +void +TcpRateLinuxBasicTest::SkbDelivered (TcpTxItem * skb) +{ + m_rateOps.SkbDelivered (skb); + m_inFlight -= skb->GetSeqSize (); + m_delivered += skb->GetSeqSize (); + m_deliveredTime = Simulator::Now (); + + NS_TEST_ASSERT_MSG_EQ (skb->GetRateInformation ().m_deliveredTime, Time::Max (), "SKB should have delivered time as Time::Max ()"); + + if (m_testCase == 1) + { + NS_TEST_ASSERT_MSG_EQ (skb->GetRateInformation ().m_isAppLimited, false, "Socket should not be applimited"); + } + else if (m_testCase == 2) + { + NS_TEST_ASSERT_MSG_EQ (skb->GetRateInformation ().m_isAppLimited, true, "Socket should be applimited"); + } +} + +/** + * \ingroup internet-test + * \ingroup tests + * + * \brief Behaves as NewReno except HasCongControl returns true + */ +class MimicCongControl : public TcpNewReno +{ +public: + /** + * \brief Get the type ID. + * \return the object TypeId + */ + static TypeId GetTypeId (void); + + MimicCongControl () + { + } + + virtual bool HasCongControl () const + { + return true; + } + +}; + +TypeId +MimicCongControl::GetTypeId (void) +{ + static TypeId tid = TypeId ("ns3::MimicCongControl") + .SetParent () + .AddConstructor () + .SetGroupName ("Internet") + ; + return tid; +} + +/** + * \ingroup internet-tests + * \ingroup tests + * + * \brief The TcpRateLinux Test uses sender-receiver model to test its functionality. + * This test case uses the bytes inflight trace to check whether rate sample + * correctly sets the value of m_deliveredTime and m_firstSentTime. This is + * done using rate trace. Further, Using Rx trace, m_isDupAck is maintained to + * track duplicate acknowledgments. This, in turn, is used to see whether rate + * sample is updated properly (in case of SACK) or not (in case of non SACK). + */ +class TcpRateLinuxWithSocketsTest : public TcpGeneralTest +{ +public: + /** + * \brief Constructor. + * \param desc Description. + * \param sackEnabled To use SACK or not + * \param toDrop Packets to drop. + */ + TcpRateLinuxWithSocketsTest (const std::string &desc, bool sackEnabled, + std::vector &toDrop); + +protected: + /** + * \brief Create and install the socket to install on the sender + * \param node sender node pointer + * \return the socket to be installed in the sender + */ + virtual Ptr CreateSenderSocket (Ptr node); + + /** + * \brief Create a receiver error model. + * \returns The receiver error model. + */ + virtual Ptr CreateReceiverErrorModel (); + + /** + * \brief Receive a packet. + * \param p The packet. + * \param h The TCP header. + * \param who Who the socket belongs to (sender or receiver). + */ + virtual void Rx (const Ptr p, const TcpHeader&h, SocketWho who); + + /** + * \brief Track the bytes in flight. + * \param oldValue previous value. + * \param newValue actual value. + */ + virtual void BytesInFlightTrace (uint32_t oldValue, uint32_t newValue); + + /** + * \brief Called when a packet is dropped. + * \param ipH The IP header. + * \param tcpH The TCP header. + * \param p The packet. + */ + void PktDropped (const Ipv4Header &ipH, const TcpHeader& tcpH, Ptr p); + + /** + * \brief Configure the test. + */ + void ConfigureEnvironment (); + + /** + * \brief Do the final checks. + */ + void FinalChecks (); + + /** + * \brief Track the rate value of TcpRateLinux. + * \param rate updated value of TcpRate. + */ + virtual void RateUpdatedTrace (const TcpRateLinux::TcpRateConnection &rate); + + /** + * \brief Track the rate sample value of TcpRateLinux. + * \param sample updated value of TcpRateSample. + */ + virtual void RateSampleUpdatedTrace (const TcpRateLinux::TcpRateSample &sample); + +private: + Ptr m_congCtl; //!< Dummy congestion control. + bool m_sackEnabled; //!< Sack Variable + std::vector m_toDrop; //!< List of SequenceNumber to drop + uint32_t m_bytesInFlight; //!< Bytes inflight + SequenceNumber32 m_lastAckRecv {SequenceNumber32 (1)}; //!< Last ACK received. + bool m_isDupAck; //!< Whether ACK is DupAck + TcpRateLinux::TcpRateConnection m_prevRate; //!< Previous rate + TcpRateLinux::TcpRateSample m_prevRateSample; //!< Previous rate sample +}; + +TcpRateLinuxWithSocketsTest::TcpRateLinuxWithSocketsTest (const std::string &desc, bool sackEnabled, + std::vector &toDrop) + : TcpGeneralTest (desc), + m_sackEnabled (sackEnabled), + m_toDrop (toDrop) +{ +} + +Ptr +TcpRateLinuxWithSocketsTest::CreateSenderSocket (Ptr node) +{ + Ptr s = TcpGeneralTest::CreateSenderSocket (node); + m_congCtl = CreateObject (); + s->SetCongestionControlAlgorithm (m_congCtl); + return s; +} + +void +TcpRateLinuxWithSocketsTest::ConfigureEnvironment () +{ + TcpGeneralTest::ConfigureEnvironment (); + SetAppPktCount (300); + SetPropagationDelay (MilliSeconds (50)); + SetTransmitStart (Seconds (2.0)); + + Config::SetDefault ("ns3::TcpSocketBase::Sack", BooleanValue (m_sackEnabled)); +} + +Ptr +TcpRateLinuxWithSocketsTest::CreateReceiverErrorModel () +{ + Ptr m_errorModel = CreateObject (); + for (std::vector::iterator it = m_toDrop.begin (); it != m_toDrop.end (); ++it) + { + m_errorModel->AddSeqToKill (SequenceNumber32 (*it)); + } + + m_errorModel->SetDropCallback (MakeCallback (&TcpRateLinuxWithSocketsTest::PktDropped, this)); + + return m_errorModel; +} + +void +TcpRateLinuxWithSocketsTest::PktDropped (const Ipv4Header &ipH, const TcpHeader &tcpH, + Ptr p) +{ + NS_LOG_DEBUG ("Drop seq= " << tcpH.GetSequenceNumber () << " size " << p->GetSize ()); +} + +void +TcpRateLinuxWithSocketsTest::Rx (const Ptr p, const TcpHeader &h, SocketWho who) +{ + if (who == SENDER) + { + if (h.GetAckNumber () == m_lastAckRecv + && m_lastAckRecv != SequenceNumber32 (1) + && (h.GetFlags () & TcpHeader::FIN) == 0) + { + m_isDupAck = true; + } + else + { + m_isDupAck = false; + m_lastAckRecv = h.GetAckNumber (); + } + } +} + +void +TcpRateLinuxWithSocketsTest::BytesInFlightTrace (uint32_t oldValue, uint32_t newValue) +{ + NS_UNUSED (oldValue); + m_bytesInFlight = newValue; +} + +void +TcpRateLinuxWithSocketsTest::RateUpdatedTrace (const TcpRateLinux::TcpRateConnection &rate) +{ + NS_LOG_DEBUG ("Rate updated " << rate); + if (m_bytesInFlight == 0) + { + NS_TEST_ASSERT_MSG_EQ (rate.m_firstSentTime, Simulator::Now (), "FirstSentTime should be current time when bytes inflight is zero"); + NS_TEST_ASSERT_MSG_EQ (rate.m_deliveredTime, Simulator::Now (), "Delivered time should be current time when bytes inflight is zero"); + } + NS_TEST_ASSERT_MSG_GT_OR_EQ (rate.m_delivered, m_prevRate.m_delivered, "Total delivered should not be lesser than previous values"); + m_prevRate = rate; +} + +void +TcpRateLinuxWithSocketsTest::RateSampleUpdatedTrace (const TcpRateLinux::TcpRateSample &sample) +{ + NS_LOG_DEBUG ("Rate sample updated " << sample); + if (m_isDupAck) + { + if (!m_sackEnabled) + { + NS_TEST_ASSERT_MSG_EQ (m_prevRateSample, sample, "RateSample should not update due to DupAcks"); + } + else + { + if (sample.m_ackedSacked == 0) + { + NS_TEST_ASSERT_MSG_EQ (m_prevRateSample, sample, "RateSample should not update as nothing is acked or sacked"); + } + } + } + m_prevRateSample = sample; +} + +void +TcpRateLinuxWithSocketsTest::FinalChecks () +{ +} + +/** + * \ingroup internet-tests + * \ingroup tests + * + * \brief The TcpRateLinuxWithBufferTest tests rate sample functionality with arbitary SACK scenario. + * Check the value of delivered against a home-made guess + */ +class TcpRateLinuxWithBufferTest : public TestCase +{ +public: + /** + * \brief Constructor. + * \param segmentSize Segment size to use. + * \param desc Description. + */ + TcpRateLinuxWithBufferTest (uint32_t segmentSize, std::string desc); + +private: + virtual void DoRun (void); + virtual void DoTeardown (void); + + /** + * \brief Track the rate value of TcpRateLinux. + * \param rate updated value of TcpRate. + */ + virtual void RateUpdatedTrace (const TcpRateLinux::TcpRateConnection &rate); + + /** + * \brief Track the rate sample value of TcpRateLinux. + * \param sample updated value of TcpRateSample. + */ + virtual void RateSampleUpdatedTrace (const TcpRateLinux::TcpRateSample &sample); + + /** \brief Test with acks without drop */ + void TestWithStraightAcks (); + + /** \brief Test with arbitary SACK scenario */ + void TestWithSackBlocks (); + + uint32_t m_expectedDelivered {0}; //!< Amount of expected delivered data + uint32_t m_expectedAckedSacked {0}; //!< Amount of expected acked sacked data + uint32_t m_segmentSize; //!< Segment size + TcpTxBuffer m_txBuf; //!< Tcp Tx buffer + Ptr m_rateOps; //!< Rate operations +}; + +TcpRateLinuxWithBufferTest::TcpRateLinuxWithBufferTest (uint32_t segmentSize, + std::string testString) + : TestCase (testString), + m_segmentSize (segmentSize) +{ + m_rateOps = CreateObject (); + m_rateOps->TraceConnectWithoutContext ("TcpRateUpdated", + MakeCallback (&TcpRateLinuxWithBufferTest::RateUpdatedTrace, this)); + m_rateOps->TraceConnectWithoutContext ("TcpRateSampleUpdated", + MakeCallback (&TcpRateLinuxWithBufferTest::RateSampleUpdatedTrace, this)); +} + +void +TcpRateLinuxWithBufferTest::DoRun () +{ + Simulator::Schedule (Seconds (0.0), &TcpRateLinuxWithBufferTest::TestWithSackBlocks, this); + Simulator::Run (); + Simulator::Destroy (); +} + +void +TcpRateLinuxWithBufferTest::RateUpdatedTrace (const TcpRateLinux::TcpRateConnection &rate) +{ + NS_LOG_DEBUG ("Rate updated " << rate); + NS_TEST_ASSERT_MSG_EQ (rate.m_delivered, m_expectedDelivered, "Delivered data is not equal to expected delivered"); +} + +void +TcpRateLinuxWithBufferTest::RateSampleUpdatedTrace (const TcpRateLinux::TcpRateSample &sample) +{ + NS_LOG_DEBUG ("Rate sample updated " << sample); + NS_TEST_ASSERT_MSG_EQ (sample.m_ackedSacked, m_expectedAckedSacked, "AckedSacked bytes is not equal to expected AckedSacked bytes"); +} + +void +TcpRateLinuxWithBufferTest::TestWithSackBlocks () +{ + SequenceNumber32 head (1); + m_txBuf.SetHeadSequence (head); + SequenceNumber32 ret; + Ptr sack = CreateObject (); + m_txBuf.SetSegmentSize (m_segmentSize); + m_txBuf.SetDupAckThresh (3); + + m_txBuf.Add(Create (10 * m_segmentSize)); + + // Send 10 Segments + for (uint8_t i = 0; i < 10; ++i) + { + bool isStartOfTransmission = m_txBuf.BytesInFlight () == 0; + TcpTxItem *outItem = m_txBuf.CopyFromSequence (m_segmentSize, SequenceNumber32((i * m_segmentSize) + 1)); + m_rateOps->SkbSent (outItem, isStartOfTransmission); + } + + uint32_t priorInFlight = m_txBuf.BytesInFlight (); + // ACK 2 Segments + for (uint8_t i = 1; i <= 2; ++i) + { + priorInFlight = m_txBuf.BytesInFlight (); + m_expectedDelivered += m_segmentSize; + m_txBuf.DiscardUpTo (SequenceNumber32 (m_segmentSize * i + 1), MakeCallback (&TcpRateOps::SkbDelivered, m_rateOps)); + m_expectedAckedSacked = m_segmentSize; + m_rateOps->SampleGen (m_segmentSize, 0, false, priorInFlight, Seconds (0)); + } + + priorInFlight = m_txBuf.BytesInFlight (); + sack->AddSackBlock (TcpOptionSack::SackBlock (SequenceNumber32 (m_segmentSize * 4 + 1), SequenceNumber32 (m_segmentSize * 5 + 1))); + m_expectedDelivered += m_segmentSize; + m_txBuf.Update(sack->GetSackList(), MakeCallback (&TcpRateOps::SkbDelivered, m_rateOps)); + m_expectedAckedSacked = m_segmentSize; + m_rateOps->SampleGen (m_segmentSize, 0, false, priorInFlight, Seconds (0)); + + priorInFlight = m_txBuf.BytesInFlight (); + sack->AddSackBlock (TcpOptionSack::SackBlock (SequenceNumber32 (m_segmentSize * 3 + 1), SequenceNumber32 (m_segmentSize * 4 + 1))); + m_expectedDelivered += m_segmentSize; + m_txBuf.Update(sack->GetSackList(), MakeCallback (&TcpRateOps::SkbDelivered, m_rateOps)); + m_rateOps->SampleGen (m_segmentSize, 0, false, priorInFlight, Seconds (0)); + + priorInFlight = m_txBuf.BytesInFlight (); + // Actual delivered should be increased by one segment even multiple blocks are acked. + m_expectedDelivered += m_segmentSize; + m_txBuf.DiscardUpTo (SequenceNumber32 (m_segmentSize * 5 + 1), MakeCallback (&TcpRateOps::SkbDelivered, m_rateOps)); + m_rateOps->SampleGen (m_segmentSize, 0, false, priorInFlight, Seconds (0)); + + + priorInFlight = m_txBuf.BytesInFlight (); + // ACK rest of the segments + for (uint8_t i = 6; i <= 10; ++i) + { + m_expectedDelivered += m_segmentSize; + m_txBuf.DiscardUpTo (SequenceNumber32 (m_segmentSize * i + 1), MakeCallback (&TcpRateOps::SkbDelivered, m_rateOps)); + } + m_expectedAckedSacked = 5 * m_segmentSize; + TcpRateOps::TcpRateSample rateSample = m_rateOps->SampleGen (5 * m_segmentSize, 0, false, priorInFlight, Seconds (0)); +} + +void +TcpRateLinuxWithBufferTest::DoTeardown () +{ +} + + +/** + * \ingroup internet-test + * \ingroup tests + * + * \brief the TestSuite for the TcpRateLinux test case + */ +class TcpRateOpsTestSuite : public TestSuite +{ +public: + TcpRateOpsTestSuite () + : TestSuite ("tcp-rate-ops", UNIT) + { + AddTestCase (new TcpRateLinuxBasicTest (1000, SequenceNumber32 (20), SequenceNumber32 (11), 1, 0, 0, "Testing SkbDelivered and SkbSent"), TestCase::QUICK); + AddTestCase (new TcpRateLinuxBasicTest (1000, SequenceNumber32 (11), SequenceNumber32 (11), 2, 0, 0, "Testing SkbDelivered and SkbSent with app limited data"), TestCase::QUICK); + + std::vector toDrop; + toDrop.push_back (4001); + AddTestCase (new TcpRateLinuxWithSocketsTest ("Checking Rate sample value without SACK, one drop", false, toDrop), + TestCase::QUICK); + + AddTestCase (new TcpRateLinuxWithSocketsTest ("Checking Rate sample value with SACK, one drop", true, toDrop), + TestCase::QUICK); + toDrop.push_back (4001); + AddTestCase (new TcpRateLinuxWithSocketsTest ("Checking Rate sample value without SACK, two drop", false, toDrop), + TestCase::QUICK); + + AddTestCase (new TcpRateLinuxWithSocketsTest ("Checking Rate sample value with SACK, two drop", true, toDrop), + TestCase::QUICK); + + AddTestCase (new TcpRateLinuxWithBufferTest (1000, "Checking rate sample values with arbitary SACK Block"), TestCase::QUICK); + + AddTestCase (new TcpRateLinuxWithBufferTest (500, "Checking rate sample values with arbitary SACK Block"), TestCase::QUICK); + + } +}; + +static TcpRateOpsTestSuite g_TcpRateOpsTestSuite; //!< Static variable for test initialization diff --git a/src/internet/test/tcp-tx-buffer-test.cc b/src/internet/test/tcp-tx-buffer-test.cc index 17aeb337b72..d9dfde29dd5 100644 --- a/src/internet/test/tcp-tx-buffer-test.cc +++ b/src/internet/test/tcp-tx-buffer-test.cc @@ -284,7 +284,7 @@ TcpTxBufferTestCase::TestNewBlock () NS_TEST_ASSERT_MSG_EQ (txBuf.BytesInFlight (), 0, "TxBuf miscalculates size of in flight segments"); - Ptr ret = txBuf.CopyFromSequence (100, SequenceNumber32 (1)); + Ptr ret = txBuf.CopyFromSequence (100, SequenceNumber32 (1))->GetPacketCopy (); NS_TEST_ASSERT_MSG_EQ (ret->GetSize (), 100, "Returned packet has different size than requested"); NS_TEST_ASSERT_MSG_EQ (txBuf.SizeFromSequence (SequenceNumber32 (1)), 100, @@ -303,7 +303,7 @@ TcpTxBufferTestCase::TestNewBlock () Ptr p2 = Create (100); txBuf.Add (p2); - ret = txBuf.CopyFromSequence (50, SequenceNumber32 (101)); + ret = txBuf.CopyFromSequence (50, SequenceNumber32 (101))->GetPacketCopy (); NS_TEST_ASSERT_MSG_EQ (ret->GetSize (), 50, "Returned packet has different size than requested"); NS_TEST_ASSERT_MSG_EQ (txBuf.SizeFromSequence (SequenceNumber32 (151)), 50, @@ -315,7 +315,7 @@ TcpTxBufferTestCase::TestNewBlock () Ptr p3 = Create (100); txBuf.Add (p3); - ret = txBuf.CopyFromSequence (70, SequenceNumber32 (151)); + ret = txBuf.CopyFromSequence (70, SequenceNumber32 (151))->GetPacketCopy (); NS_TEST_ASSERT_MSG_EQ (ret->GetSize (), 70, "Returned packet has different size than requested"); NS_TEST_ASSERT_MSG_EQ (txBuf.SizeFromSequence (SequenceNumber32 (221)), 80, @@ -323,7 +323,7 @@ TcpTxBufferTestCase::TestNewBlock () NS_TEST_ASSERT_MSG_EQ (txBuf.BytesInFlight (), 120, "TxBuf miscalculates size of in flight segments"); - ret = txBuf.CopyFromSequence (3000, SequenceNumber32 (221)); + ret = txBuf.CopyFromSequence (3000, SequenceNumber32 (221))->GetPacketCopy (); NS_TEST_ASSERT_MSG_EQ (ret->GetSize (), 80, "Returned packet has different size than requested"); NS_TEST_ASSERT_MSG_EQ (txBuf.SizeFromSequence (SequenceNumber32 (301)), 0, diff --git a/src/internet/wscript b/src/internet/wscript index 571afc86d9d..e317672b08e 100644 --- a/src/internet/wscript +++ b/src/internet/wscript @@ -162,6 +162,8 @@ def build(bld): 'model/tcp-lp.cc', 'model/tcp-rx-buffer.cc', 'model/tcp-tx-buffer.cc', + 'model/tcp-tx-item.cc', + 'model/tcp-rate-ops.cc', 'model/tcp-option.cc', 'model/tcp-option-rfc793.cc', 'model/tcp-option-winscale.cc', @@ -293,6 +295,7 @@ def build(bld): 'test/tcp-datasentcb-test.cc', 'test/ipv4-rip-test.cc', 'test/tcp-close-test.cc', + 'test/tcp-rate-ops-test.cc', ] privateheaders = bld(features='ns3privateheader') privateheaders.module = 'internet' @@ -397,6 +400,8 @@ def build(bld): 'model/tcp-socket-base.h', 'model/tcp-socket-state.h', 'model/tcp-tx-buffer.h', + 'model/tcp-tx-item.h', + 'model/tcp-rate-ops.h', 'model/tcp-rx-buffer.h', 'model/tcp-recovery-ops.h', 'model/tcp-prr-recovery.h',