tcp: Align PRR implementation with RFC 6937 bis-08
Co-authored-by: Aniket Singh <aniketsingh84646@gmail.com>
This commit is contained in:
@@ -43,6 +43,7 @@ been tested on Linux. As of this release, the latest known version to work with
|
||||
- (lr-wpan) !2163 - Lr-wpan module now uses the `ns3::lrwpan` namespace in its TypeId.
|
||||
- (internet) !2027 - TcpSocketBase: Added TCP retransmission trace
|
||||
- (applications) !2027 - BulkSendApplication: Added TCP retransmission trace consuming TcpSocketBase's TCP retransmission trace
|
||||
- (tcp) !2059 - Aligns PRR implementation with RFC 6937 bis-08. Added a new param `isDupAck` to `DoRecovery` method, removed `ReductionBound` attribute from `TcpPrrRecovery`.
|
||||
|
||||
### Bugs fixed
|
||||
|
||||
|
||||
@@ -1662,6 +1662,42 @@ After limit calculation, the cWnd is updated as given below:
|
||||
.. math:: sndcnt = MIN (ssThresh - pipe, limit)
|
||||
.. math:: cWnd = pipe + sndcnt
|
||||
|
||||
Thanks to Neal Cardwell for providing the following documentation of |ns3| PRR model:
|
||||
The |ns3| implementation of PRR has something like this (note that m_isRetransDataAcked is true exactly when retransmitted data is cumulatively ACKed, and there is no RACK-TLP or similar mechanism to detect lost retransmits as of now):
|
||||
|
||||
::
|
||||
|
||||
// PRR-CRB by default
|
||||
int limit = std::max(m_prrDelivered - m_prrOut, deliveredBytes);
|
||||
// safeACK should be true iff ACK advances SND.UNA with no further loss indicated.
|
||||
// We approximate that here (given the current lack of RACK-TLP in ns-3):
|
||||
bool safeACK = tcb->m_isRetransDataAcked; // retransmit cumulatively ACKed?
|
||||
if (safeACK) // PRR-SSRB when recovery makes good progress
|
||||
limit += tcb->m_segmentSize;
|
||||
// Attempt to catch up, as permitted
|
||||
sendCount = std::min(limit, static_cast<int>(tcb->m_ssThresh - tcb->m_bytesInFlight));
|
||||
}
|
||||
// Force a fast retransmit upon entering fast recovery:
|
||||
if (m_prrOut == 0 && sendCount == 0)
|
||||
sendCount = tcb->m_segmentSize;
|
||||
|
||||
which is close to the Linux implementation (which matches the RFC):
|
||||
|
||||
::
|
||||
|
||||
int delta = tp->snd_ssthresh - tcp_packets_in_flight(tp);
|
||||
...
|
||||
sndcnt = max_t(int, tp->prr_delivered - tp->prr_out,
|
||||
newly_acked_sacked);
|
||||
if (flag & FLAG_SND_UNA_ADVANCED && !newly_lost)
|
||||
sndcnt++;
|
||||
sndcnt = min(delta, sndcnt);
|
||||
}
|
||||
/* Force a fast retransmit upon entering fast recovery */
|
||||
sndcnt = max(sndcnt, (tp->prr_out ? 0 : 1));
|
||||
|
||||
For reference, the Linux TCP PRR implementation is entirely contained in tcp_init_cwnd_reduction(), tcp_cwnd_reduction(), tcp_end_cwnd_reduction() and the updates elsewhere to prr_out.
|
||||
|
||||
More information (paper): https://dl.acm.org/citation.cfm?id=2068832
|
||||
|
||||
More information (RFC): https://tools.ietf.org/html/rfc6937
|
||||
|
||||
@@ -24,16 +24,10 @@ NS_OBJECT_ENSURE_REGISTERED(TcpPrrRecovery);
|
||||
TypeId
|
||||
TcpPrrRecovery::GetTypeId()
|
||||
{
|
||||
static TypeId tid =
|
||||
TypeId("ns3::TcpPrrRecovery")
|
||||
.SetParent<TcpClassicRecovery>()
|
||||
.AddConstructor<TcpPrrRecovery>()
|
||||
.SetGroupName("Internet")
|
||||
.AddAttribute("ReductionBound",
|
||||
"Type of Reduction Bound",
|
||||
EnumValue(SSRB),
|
||||
MakeEnumAccessor<ReductionBound_t>(&TcpPrrRecovery::m_reductionBoundMode),
|
||||
MakeEnumChecker(CRB, "CRB", SSRB, "SSRB"));
|
||||
static TypeId tid = TypeId("ns3::TcpPrrRecovery")
|
||||
.SetParent<TcpClassicRecovery>()
|
||||
.AddConstructor<TcpPrrRecovery>()
|
||||
.SetGroupName("Internet");
|
||||
return tid;
|
||||
}
|
||||
|
||||
@@ -47,8 +41,7 @@ TcpPrrRecovery::TcpPrrRecovery(const TcpPrrRecovery& recovery)
|
||||
: TcpClassicRecovery(recovery),
|
||||
m_prrDelivered(recovery.m_prrDelivered),
|
||||
m_prrOut(recovery.m_prrOut),
|
||||
m_recoveryFlightSize(recovery.m_recoveryFlightSize),
|
||||
m_reductionBoundMode(recovery.m_reductionBoundMode)
|
||||
m_recoveryFlightSize(recovery.m_recoveryFlightSize)
|
||||
{
|
||||
NS_LOG_FUNCTION(this);
|
||||
}
|
||||
@@ -68,37 +61,50 @@ TcpPrrRecovery::EnterRecovery(Ptr<TcpSocketState> tcb,
|
||||
|
||||
m_prrOut = 0;
|
||||
m_prrDelivered = 0;
|
||||
m_recoveryFlightSize = unAckDataCount;
|
||||
m_recoveryFlightSize = tcb->m_bytesInFlight; // RFC 6675 pipe before recovery
|
||||
|
||||
DoRecovery(tcb, deliveredBytes);
|
||||
DoRecovery(tcb, deliveredBytes, true);
|
||||
}
|
||||
|
||||
void
|
||||
TcpPrrRecovery::DoRecovery(Ptr<TcpSocketState> tcb, uint32_t deliveredBytes)
|
||||
TcpPrrRecovery::DoRecovery(Ptr<TcpSocketState> tcb, uint32_t deliveredBytes, bool isDupAck)
|
||||
{
|
||||
NS_LOG_FUNCTION(this << tcb << deliveredBytes);
|
||||
|
||||
if (isDupAck && m_prrDelivered < m_recoveryFlightSize)
|
||||
{
|
||||
deliveredBytes += tcb->m_segmentSize;
|
||||
}
|
||||
if (deliveredBytes == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_prrDelivered += deliveredBytes;
|
||||
|
||||
int sendCount;
|
||||
if (tcb->m_bytesInFlight > tcb->m_ssThresh)
|
||||
{
|
||||
// Proportional Rate Reductions
|
||||
sendCount =
|
||||
std::ceil(m_prrDelivered * tcb->m_ssThresh * 1.0 / m_recoveryFlightSize) - m_prrOut;
|
||||
}
|
||||
else
|
||||
{
|
||||
int limit = static_cast<int>(tcb->m_ssThresh - tcb->m_bytesInFlight);
|
||||
if (m_reductionBoundMode == CRB)
|
||||
// PRR-CRB by default
|
||||
int limit = std::max(m_prrDelivered - m_prrOut, deliveredBytes);
|
||||
|
||||
// safeACK should be true iff ACK advances SND.UNA with no further loss indicated.
|
||||
// We approximate that here (given the current lack of RACK-TLP in ns-3)
|
||||
bool safeACK = tcb->m_isRetransDataAcked; // retransmit cumulatively ACKed?
|
||||
|
||||
if (safeACK)
|
||||
{
|
||||
limit = m_prrDelivered - m_prrOut;
|
||||
}
|
||||
else if (m_reductionBoundMode == SSRB)
|
||||
{
|
||||
if (tcb->m_isRetransDataAcked)
|
||||
{
|
||||
limit = std::max(m_prrDelivered - m_prrOut, deliveredBytes) + tcb->m_segmentSize;
|
||||
}
|
||||
// PRR-SSRB when recovery makes good progress
|
||||
limit += tcb->m_segmentSize;
|
||||
}
|
||||
|
||||
// Attempt to catch up, as permitted
|
||||
sendCount = std::min(limit, static_cast<int>(tcb->m_ssThresh - tcb->m_bytesInFlight));
|
||||
}
|
||||
|
||||
|
||||
@@ -50,15 +50,6 @@ class TcpPrrRecovery : public TcpClassicRecovery
|
||||
|
||||
~TcpPrrRecovery() override;
|
||||
|
||||
/**
|
||||
* \brief Reduction Bound variant (CRB or SSRB)
|
||||
*/
|
||||
enum ReductionBound_t
|
||||
{
|
||||
CRB, /**< Conservative Reduction Bound */
|
||||
SSRB /**< Slow Start Reduction Bound */
|
||||
};
|
||||
|
||||
std::string GetName() const override;
|
||||
|
||||
void EnterRecovery(Ptr<TcpSocketState> tcb,
|
||||
@@ -66,7 +57,7 @@ class TcpPrrRecovery : public TcpClassicRecovery
|
||||
uint32_t unAckDataCount,
|
||||
uint32_t deliveredBytes) override;
|
||||
|
||||
void DoRecovery(Ptr<TcpSocketState> tcb, uint32_t deliveredBytes) override;
|
||||
void DoRecovery(Ptr<TcpSocketState> tcb, uint32_t deliveredBytes, bool isDupAck) override;
|
||||
|
||||
void ExitRecovery(Ptr<TcpSocketState> tcb) override;
|
||||
|
||||
@@ -78,7 +69,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
|
||||
ReductionBound_t m_reductionBoundMode{SSRB}; //!< mode of Reduction Bound to be used
|
||||
};
|
||||
} // namespace ns3
|
||||
|
||||
|
||||
@@ -94,9 +94,11 @@ TcpClassicRecovery::EnterRecovery(Ptr<TcpSocketState> tcb,
|
||||
}
|
||||
|
||||
void
|
||||
TcpClassicRecovery::DoRecovery(Ptr<TcpSocketState> tcb, uint32_t deliveredBytes [[maybe_unused]])
|
||||
TcpClassicRecovery::DoRecovery(Ptr<TcpSocketState> tcb,
|
||||
uint32_t deliveredBytes [[maybe_unused]],
|
||||
bool isDupAck)
|
||||
{
|
||||
NS_LOG_FUNCTION(this << tcb << deliveredBytes);
|
||||
NS_LOG_FUNCTION(this << tcb << deliveredBytes << isDupAck);
|
||||
tcb->m_cWndInfl += tcb->m_segmentSize;
|
||||
}
|
||||
|
||||
|
||||
@@ -99,11 +99,13 @@ class TcpRecoveryOps : public Object
|
||||
* The function is called on arrival of every ack when TcpSocketState
|
||||
* is set to CA_RECOVERY. It performs the necessary cwnd changes
|
||||
* as per the recovery algorithm.
|
||||
* The param `isDupAck` has been added to align PRR implementation with RFC 6937 bis-08.
|
||||
*
|
||||
* \param tcb internal congestion state
|
||||
* \param deliveredBytes bytes (S)ACKed in the last (S)ACK
|
||||
* \param isDupAck Indicates if the last acknowledgement was duplicate.
|
||||
*/
|
||||
virtual void DoRecovery(Ptr<TcpSocketState> tcb, uint32_t deliveredBytes) = 0;
|
||||
virtual void DoRecovery(Ptr<TcpSocketState> tcb, uint32_t deliveredBytes, bool isDupAck) = 0;
|
||||
|
||||
/**
|
||||
* \brief Performs cwnd adjustments at the end of recovery
|
||||
@@ -178,7 +180,7 @@ class TcpClassicRecovery : public TcpRecoveryOps
|
||||
uint32_t unAckDataCount,
|
||||
uint32_t deliveredBytes) override;
|
||||
|
||||
void DoRecovery(Ptr<TcpSocketState> tcb, uint32_t deliveredBytes) override;
|
||||
void DoRecovery(Ptr<TcpSocketState> tcb, uint32_t deliveredBytes, bool isDupAck) override;
|
||||
|
||||
void ExitRecovery(Ptr<TcpSocketState> tcb) override;
|
||||
|
||||
|
||||
@@ -1779,7 +1779,7 @@ TcpSocketBase::DupAck(uint32_t currentDelivered)
|
||||
}
|
||||
if (!m_congestionControl->HasCongControl())
|
||||
{
|
||||
m_recoveryOps->DoRecovery(m_tcb, currentDelivered);
|
||||
m_recoveryOps->DoRecovery(m_tcb, currentDelivered, true);
|
||||
NS_LOG_INFO(m_dupAckCount << " Dupack received in fast recovery mode."
|
||||
"Increase cwnd to "
|
||||
<< m_tcb->m_cWnd);
|
||||
@@ -2085,7 +2085,7 @@ TcpSocketBase::ProcessAck(const SequenceNumber32& ackNumber,
|
||||
// there is available window
|
||||
if (!m_congestionControl->HasCongControl() && segsAcked >= 1)
|
||||
{
|
||||
m_recoveryOps->DoRecovery(m_tcb, currentDelivered);
|
||||
m_recoveryOps->DoRecovery(m_tcb, currentDelivered, false);
|
||||
}
|
||||
|
||||
// If the packet is already retransmitted do not retransmit it
|
||||
@@ -2146,7 +2146,7 @@ TcpSocketBase::ProcessAck(const SequenceNumber32& ackNumber,
|
||||
// and/or packet reordering
|
||||
if (!m_congestionControl->HasCongControl() && segsAcked >= 1)
|
||||
{
|
||||
m_recoveryOps->DoRecovery(m_tcb, currentDelivered);
|
||||
m_recoveryOps->DoRecovery(m_tcb, currentDelivered, false);
|
||||
}
|
||||
NewAck(ackNumber, true);
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ ClassicRecoveryTest::DoRun()
|
||||
|
||||
uint32_t cWndInflPrevious = m_state->m_cWndInfl;
|
||||
uint32_t cWndPrevious = m_state->m_cWnd;
|
||||
recovery->DoRecovery(m_state, 500);
|
||||
recovery->DoRecovery(m_state, 500, false);
|
||||
NS_TEST_ASSERT_MSG_EQ(
|
||||
m_state->m_cWndInfl,
|
||||
(cWndInflPrevious + m_state->m_segmentSize),
|
||||
|
||||
@@ -36,7 +36,6 @@ class PrrRecoveryTest : public TestCase
|
||||
* \param bytesInFlight Current bytes in flight.
|
||||
* \param m_deliveredBytes Bytes SACKed on last acknowledgment.
|
||||
* \param bytesSent Bytes sent while in recovery phase.
|
||||
* \param reductionBound Type of reduction bound to be used.
|
||||
* \param name Test description.
|
||||
*/
|
||||
PrrRecoveryTest(uint32_t cWnd,
|
||||
@@ -46,20 +45,18 @@ class PrrRecoveryTest : public TestCase
|
||||
uint32_t bytesInFlight,
|
||||
uint32_t m_deliveredBytes,
|
||||
uint32_t bytesSent,
|
||||
const std::string& reductionBound,
|
||||
const std::string& name);
|
||||
|
||||
private:
|
||||
void DoRun() override;
|
||||
|
||||
uint32_t m_cWnd; //!< Congestion window.
|
||||
uint32_t m_segmentSize; //!< Segment size.
|
||||
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_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.
|
||||
uint32_t m_cWnd; //!< Congestion window.
|
||||
uint32_t m_segmentSize; //!< Segment size.
|
||||
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_deliveredBytes; //!< Bytes SACKed on last acknowledgment.
|
||||
uint32_t m_bytesSent; //!< Bytes sent while in recovery phase.
|
||||
|
||||
Ptr<TcpSocketState> m_state; //!< TCP socket state.
|
||||
};
|
||||
@@ -71,7 +68,6 @@ PrrRecoveryTest::PrrRecoveryTest(uint32_t cWnd,
|
||||
uint32_t bytesInFlight,
|
||||
uint32_t deliveredBytes,
|
||||
uint32_t bytesSent,
|
||||
const std::string& reductionBound,
|
||||
const std::string& name)
|
||||
: TestCase(name),
|
||||
m_cWnd(cWnd),
|
||||
@@ -80,8 +76,7 @@ PrrRecoveryTest::PrrRecoveryTest(uint32_t cWnd,
|
||||
m_unAckDataCount(unAckDataCount),
|
||||
m_bytesInFlight(bytesInFlight),
|
||||
m_deliveredBytes(deliveredBytes),
|
||||
m_bytesSent(bytesSent),
|
||||
m_reductionBound(reductionBound)
|
||||
m_bytesSent(bytesSent)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -97,7 +92,6 @@ PrrRecoveryTest::DoRun()
|
||||
m_state->m_bytesInFlight = m_bytesInFlight;
|
||||
|
||||
Ptr<TcpPrrRecovery> recovery = CreateObject<TcpPrrRecovery>();
|
||||
recovery->SetAttribute("ReductionBound", StringValue(m_reductionBound));
|
||||
|
||||
recovery->EnterRecovery(m_state, 3, m_unAckDataCount, 0);
|
||||
|
||||
@@ -114,7 +108,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, m_deliveredBytes);
|
||||
recovery->DoRecovery(m_state, m_deliveredBytes, false);
|
||||
|
||||
if (m_bytesInFlight > m_state->m_ssThresh)
|
||||
{
|
||||
@@ -151,7 +145,6 @@ class PrrRecoveryTestSuite : public TestSuite
|
||||
3000,
|
||||
500,
|
||||
1000,
|
||||
"SSRB",
|
||||
"Prr test on cWnd when bytesInFlight is greater than ssThresh with SSRB"),
|
||||
TestCase::Duration::QUICK);
|
||||
AddTestCase(new PrrRecoveryTest(
|
||||
@@ -162,7 +155,6 @@ class PrrRecoveryTestSuite : public TestSuite
|
||||
1000,
|
||||
500,
|
||||
1000,
|
||||
"SSRB",
|
||||
"Prr test on cWnd when bytesInFlight is lower than ssThresh with SSRB"),
|
||||
TestCase::Duration::QUICK);
|
||||
AddTestCase(new PrrRecoveryTest(
|
||||
@@ -173,7 +165,6 @@ class PrrRecoveryTestSuite : public TestSuite
|
||||
3000,
|
||||
500,
|
||||
1000,
|
||||
"CRB",
|
||||
"Prr test on cWnd when bytesInFlight is greater than ssThresh with CRB"),
|
||||
TestCase::Duration::QUICK);
|
||||
AddTestCase(new PrrRecoveryTest(
|
||||
@@ -184,7 +175,6 @@ class PrrRecoveryTestSuite : public TestSuite
|
||||
1000,
|
||||
500,
|
||||
1000,
|
||||
"CRB",
|
||||
"Prr test on cWnd when bytesInFlight is lower than ssThresh with CRB"),
|
||||
TestCase::Duration::QUICK);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user