tcp: RFC 6675-compliant Fast Retransmit/Recovery

This commit is contained in:
Natale Patriciello
2017-02-03 14:02:21 +01:00
parent 48d51f9c26
commit efc5f3c53b
2 changed files with 270 additions and 182 deletions

View File

@@ -1524,37 +1524,59 @@ TcpSocketBase::LimitedTransmit ()
{
NS_LOG_FUNCTION (this);
NS_ASSERT (m_limitedTx);
NS_ASSERT (m_txBuffer->SizeFromSequence (m_tcb->m_nextTxSequence) > 0);
NS_LOG_INFO ("Limited transmit");
uint32_t sz = SendDataPacket (m_tcb->m_nextTxSequence, m_tcb->m_segmentSize, true);
m_tcb->m_nextTxSequence += sz;
// RFC 6675, Section 5, point 3, continuing :
// (3.2) Run SetPipe ().
// (3.3) If (cwnd - pipe) >= 1 SMSS, there exists previously unsent
// data, and the receiver's advertised window allows, transmit
// up to 1 SMSS of data starting with the octet HighData+1 and
// update HighData to reflect this transmission, then return
// to (3.2).
// (3.2) and (3.1) done in SendPendingData
SendPendingData (m_connected);
}
void
TcpSocketBase::FastRetransmit ()
TcpSocketBase::EnterRecovery ()
{
NS_LOG_FUNCTION (this);
NS_ASSERT (m_tcb->m_congState != TcpSocketState::CA_RECOVERY);
NS_LOG_DEBUG (TcpSocketState::TcpCongStateName[m_tcb->m_congState] <<
" -> CA_RECOVERY");
// RFC 6675, point (4):
// (4) Invoke fast retransmit and enter loss recovery as follows:
// (4.1) RecoveryPoint = HighData
m_recover = m_tcb->m_highTxMark;
m_congestionControl->CongestionStateSet (m_tcb, TcpSocketState::CA_RECOVERY);
m_tcb->m_congState = TcpSocketState::CA_RECOVERY;
// (4.2) ssthresh = cwnd = (FlightSize / 2)
m_tcb->m_ssThresh = m_congestionControl->GetSsThresh (m_tcb,
BytesInFlight ());
m_tcb->m_cWnd = m_tcb->m_ssThresh + m_dupAckCount * m_tcb->m_segmentSize;
m_tcb->m_cWnd = m_tcb->m_ssThresh;
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);
// (4.3) Retransmit the first data segment presumed dropped
DoRetransmit ();
// (4.4) Run SetPipe ()
// (4.5) Proceed to step (C)
// the step C is done after the ProcessAck function (SendPendingData)
}
void
TcpSocketBase::DupAck ()
{
NS_LOG_FUNCTION (this);
// RFC 6675, Section 5:
// the TCP MUST increase DupAcks by one ...
// NOTE: We count also the dupAcks received in CA_RECOVERY
++m_dupAckCount;
if (m_tcb->m_congState == TcpSocketState::CA_OPEN)
@@ -1571,29 +1593,47 @@ TcpSocketBase::DupAck ()
if (m_tcb->m_congState == TcpSocketState::CA_DISORDER)
{
// RFC 6675, Section 5, continuing:
// ... and take the following steps:
// (1) If DupAcks >= DupThresh, go to step (4).
if ((m_dupAckCount == m_retxThresh) && (m_highRxAckMark >= m_recover))
{
// triple duplicate ack triggers fast retransmit (RFC2582 sec.3 bullet #1)
NS_LOG_DEBUG (TcpSocketState::TcpCongStateName[m_tcb->m_congState] <<
" -> RECOVERY");
FastRetransmit ();
EnterRecovery ();
NS_ASSERT (m_tcb->m_congState == TcpSocketState::CA_RECOVERY);
}
else if (m_limitedTx && m_txBuffer->SizeFromSequence (m_tcb->m_nextTxSequence) > 0)
// (2) If DupAcks < DupThresh but IsLost (HighACK + 1) returns true
// (indicating at least three segments have arrived above the current
// cumulative acknowledgment point, which is taken to indicate loss)
// go to step (4).
else if (m_txBuffer->IsLost (m_highRxAckMark + 1, m_retxThresh, m_tcb->m_segmentSize))
{
// RFC3042 Limited transmit: Send a new packet for each duplicated ACK before fast retransmit
LimitedTransmit ();
EnterRecovery ();
NS_ASSERT (m_tcb->m_congState == TcpSocketState::CA_RECOVERY);
}
else
{
// (3) The TCP MAY transmit previously unsent data segments as per
// Limited Transmit [RFC5681] ...except that the number of octets
// which may be sent is governed by pipe and cwnd as follows:
//
// (3.1) Set HighRxt to HighACK.
// Not clear in RFC. We don't do this here, since we still have
// to retransmit the segment.
NS_ASSERT (m_dupAckCount < m_retxThresh);
if (m_limitedTx)
{
// (3.2) and (3.3) are performed in the following:
LimitedTransmit ();
}
// (3.4) Terminate processing of this ACK.
}
}
else if (m_tcb->m_congState == TcpSocketState::CA_RECOVERY)
{ // Increase cwnd for every additional dupack (RFC2582, sec.3 bullet #3)
m_tcb->m_cWnd += m_tcb->m_segmentSize;
NS_LOG_INFO (m_dupAckCount << " Dupack received in fast recovery mode."
"Increase cwnd to " << m_tcb->m_cWnd);
SendPendingData (m_connected);
}
// Artificially call PktsAcked. After all, one segment has been ACKed.
m_congestionControl->PktsAcked (m_tcb, 1, m_lastRtt);
if (m_dupAckCount > 0)
{
NS_ASSERT (m_tcb->m_congState > TcpSocketState::CA_OPEN);
}
}
/* Process the newly received ACK */
@@ -1605,33 +1645,104 @@ TcpSocketBase::ReceivedAck (Ptr<Packet> packet, const TcpHeader& tcpHeader)
NS_ASSERT (0 != (tcpHeader.GetFlags () & TcpHeader::ACK));
NS_ASSERT (m_tcb->m_segmentSize > 0);
// 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);
SequenceNumber32 ackNumber = tcpHeader.GetAckNumber ();
NS_LOG_DEBUG ("ACK of " << ackNumber <<
" SND.UNA=" << m_txBuffer->HeadSequence () <<
" SND.NXT=" << m_tcb->m_nextTxSequence);
// RFC 6675 Section 5: 2nd, 3rd paragraph and point (A), (B) implementation
// are inside the function ProcessAck
ProcessAck (ackNumber, scoreboardUpdated);
m_tcb->m_lastAckedSeq = ackNumber;
if (ackNumber == m_txBuffer->HeadSequence ()
&& ackNumber < m_tcb->m_nextTxSequence
&& packet->GetSize () == 0)
// RFC 6675, Section 5, point (C), try to send more data. NB: (C) is implemented
// inside SendPendingData
if (!m_sendPendingDataEvent.IsRunning ())
{
// There is a DupAck
m_sendPendingDataEvent = Simulator::Schedule (TimeStep (1),
&TcpSocketBase::SendPendingData,
this, m_connected);
}
// If there is any data piggybacked, store it into m_rxBuffer
if (packet->GetSize () > 0)
{
ReceivedData (packet, tcpHeader);
}
}
void
TcpSocketBase::ProcessAck (const SequenceNumber32 &ackNumber, bool scoreboardUpdated)
{
NS_LOG_FUNCTION (this << ackNumber << scoreboardUpdated);
// RFC 6675, Section 5, 2nd paragraph:
// If the incoming ACK is a cumulative acknowledgment, the TCP MUST
// reset DupAcks to zero.
uint32_t oldDupAckCount = m_dupAckCount; // remember the old value
if (ackNumber > m_txBuffer->HeadSequence ())
{
m_dupAckCount = 0;
}
m_tcb->m_lastAckedSeq = ackNumber; // Update lastAckedSeq
/* In RFC 5681 the definition of duplicate acknowledgment was strict:
*
* (a) the receiver of the ACK has outstanding data,
* (b) the incoming acknowledgment carries no data,
* (c) the SYN and FIN bits are both off,
* (d) the acknowledgment number is equal to the greatest acknowledgment
* received on the given connection (TCP.UNA from [RFC793]),
* (e) the advertised window in the incoming acknowledgment equals the
* advertised window in the last incoming acknowledgment.
*
* With RFC 6675, this definition has been reduced:
*
* (a) the ACK is carrying a SACK block that identifies previously
* unacknowledged and un-SACKed octets between HighACK (TCP.UNA) and
* HighData (m_highTxMark)
*/
// RFC 6675, Section 5, 3rd paragraph:
// If the incoming ACK is a duplicate acknowledgment per the definition
// in Section 2 (regardless of its status as a cumulative
// acknowledgment), and the TCP is not currently in loss recovery
if (scoreboardUpdated)
{
NS_LOG_DEBUG ("ACK of " << ackNumber <<
" SND.UNA=" << m_txBuffer->HeadSequence () <<
" SND.NXT=" << m_tcb->m_nextTxSequence <<
" we are ready to process the dupAck");
// loss recovery check is done inside this function thanks to
// the congestion state machine
DupAck ();
}
else if (ackNumber == m_txBuffer->HeadSequence ()
&& ackNumber == m_tcb->m_nextTxSequence)
if (ackNumber == m_txBuffer->HeadSequence ()
&& ackNumber == m_tcb->m_nextTxSequence)
{
NS_LOG_INFO ("ACK of " << ackNumber <<
", there is no need to process (we haven't data to transmit)");
// Dupack, but the ACK is precisely equal to the nextTxSequence
return;
}
else if (ackNumber == m_txBuffer->HeadSequence ()
&& ackNumber > m_tcb->m_nextTxSequence)
{
// ACK of the FIN bit ... nextTxSequence is not updated since we
// don't have anything to transmit
NS_LOG_DEBUG ("Update nextTxSequence manually to " << ackNumber);
m_tcb->m_nextTxSequence = ackNumber;
}
else if (ackNumber == m_txBuffer->HeadSequence ())
{
// DupAck. Artificially call PktsAcked: after all, one segment has been ACKed.
NS_LOG_INFO ("ACK of " << ackNumber << ", PktsAcked called (ACK already managed in DupAck)");
m_congestionControl->PktsAcked (m_tcb, 1, m_lastRtt);
}
else if (ackNumber > m_txBuffer->HeadSequence ())
{ // Case 3: New ACK, reset m_dupAckCount and update m_txBuffer
bool callCongestionControl = true;
bool resetRTO = true;
{
uint32_t bytesAcked = ackNumber - m_txBuffer->HeadSequence ();
uint32_t segsAcked = bytesAcked / m_tcb->m_segmentSize;
m_bytesAckedNotProcessed += bytesAcked % m_tcb->m_segmentSize;
@@ -1642,168 +1753,133 @@ TcpSocketBase::ReceivedAck (Ptr<Packet> packet, const TcpHeader& tcpHeader)
m_bytesAckedNotProcessed -= m_tcb->m_segmentSize;
}
NS_LOG_LOGIC (" Bytes acked: " << bytesAcked <<
" Segments acked: " << segsAcked <<
" bytes left: " << m_bytesAckedNotProcessed);
/* The following switch is made because m_dupAckCount can be
* "inflated" through out-of-order segments (e.g. from retransmission,
* while segments have not been lost but are network-reordered). At
* least one segment has been acked; in the luckiest case, an amount
* equals to segsAcked-m_dupAckCount has not been processed.
*
* To be clear: segsAcked will be passed to PktsAcked, and it should take
* in considerations the times that it has been already called, while newSegsAcked
* will be passed to IncreaseCwnd, and it represents the amount of
* segments that are allowed to increase the cWnd value.
*/
uint32_t newSegsAcked = segsAcked;
if (segsAcked > m_dupAckCount)
// RFC 6675, Section 5, part (B)
// (B) Upon receipt of an ACK that does not cover RecoveryPoint, the
// following actions MUST be taken:
//
// (B.1) Use Update () to record the new SACK information conveyed
// by the incoming ACK.
// (B.2) Use SetPipe () to re-calculate the number of octets still
// in the network.
//
// (B.1) is done at the beginning, while (B.2) is delayed to part (C) while
// trying to transmit with SendPendingData. We are not allowed to exit
// the CA_RECOVERY phase. Just process this partial ack (RFC 5681)
if (ackNumber < m_recover && m_tcb->m_congState == TcpSocketState::CA_RECOVERY)
{
segsAcked -= m_dupAckCount;
m_txBuffer->DiscardUpTo (ackNumber);
DoRetransmit (); // Assume the next seq is lost. Retransmit lost packet
// This partial ACK acknowledge the fact that one segment has been
// previously lost and now successfully received. All others have
// been processed when they come under the form of dupACKs
m_congestionControl->PktsAcked (m_tcb, 1, m_lastRtt);
NewAck (ackNumber, m_isFirstPartialAck);
if (m_isFirstPartialAck)
{
NS_LOG_DEBUG ("Partial ACK of " << ackNumber <<
" and this is the first (RTO will be reset)");
m_isFirstPartialAck = false;
}
else
{
NS_LOG_DEBUG ("Partial ACK of " << ackNumber <<
" and this is NOT the first (RTO will not be reset)");
}
}
// From RFC 6675 section 5.1
// In addition, a new recovery phase (as described in Section 5) MUST NOT
// be initiated until HighACK is greater than or equal to the new value
// of RecoveryPoint.
else if (ackNumber < m_recover && m_tcb->m_congState == TcpSocketState::CA_LOSS)
{
m_congestionControl->PktsAcked (m_tcb, segsAcked, m_lastRtt);
m_congestionControl->IncreaseWindow (m_tcb, segsAcked);
NS_LOG_DEBUG ("Ack of " << ackNumber << ", equivalent of " << segsAcked <<
" segments in CA_LOSS. Cong Control Called, cWnd=" << m_tcb->m_cWnd <<
" ssTh=" << m_tcb->m_ssThresh);
NewAck (ackNumber, true);
}
else
{
segsAcked = 1;
}
if (m_tcb->m_congState == TcpSocketState::CA_OPEN)
{
m_congestionControl->PktsAcked (m_tcb, segsAcked, m_lastRtt);
}
else if (m_tcb->m_congState == TcpSocketState::CA_DISORDER)
{
// The network reorder packets. Linux changes the counting lost
// packet algorithm from FACK to NewReno. We simply go back in Open.
m_congestionControl->CongestionStateSet (m_tcb, TcpSocketState::CA_OPEN);
m_tcb->m_congState = TcpSocketState::CA_OPEN;
m_congestionControl->PktsAcked (m_tcb, segsAcked, m_lastRtt);
m_dupAckCount = 0;
NS_LOG_DEBUG ("DISORDER -> OPEN");
}
else if (m_tcb->m_congState == TcpSocketState::CA_RECOVERY)
{
if (ackNumber < m_recover)
if (m_tcb->m_congState == TcpSocketState::CA_OPEN)
{
/* Partial ACK.
* In case of partial ACK, retransmit the first unacknowledged
* segment. Deflate the congestion window by the amount of new
* data acknowledged by the Cumulative Acknowledgment field.
* If the partial ACK acknowledges at least one SMSS of new data,
* then add back SMSS bytes to the congestion window.
* This artificially inflates the congestion window in order to
* reflect the additional segment that has left the network.
* Send a new segment if permitted by the new value of cwnd.
* This "partial window deflation" attempts to ensure that, when
* fast recovery eventually ends, approximately ssthresh amount
* of data will be outstanding in the network. Do not exit the
* fast recovery procedure (i.e., if any duplicate ACKs subsequently
* arrive, execute step 4 of Section 3.2 of [RFC5681]).
*/
m_tcb->m_cWnd = SafeSubtraction (m_tcb->m_cWnd, bytesAcked);
if (segsAcked >= 1)
NS_LOG_DEBUG (segsAcked << " segments acked in CA_OPEN, ack of " <<
ackNumber);
m_congestionControl->PktsAcked (m_tcb, segsAcked, m_lastRtt);
}
else if (m_tcb->m_congState == TcpSocketState::CA_DISORDER)
{
// The network reorder packets. Linux changes the counting lost
// packet algorithm from FACK to NewReno. We simply go back in Open.
m_congestionControl->CongestionStateSet (m_tcb, TcpSocketState::CA_OPEN);
m_tcb->m_congState = TcpSocketState::CA_OPEN;
if (segsAcked >= oldDupAckCount)
{
m_tcb->m_cWnd += m_tcb->m_segmentSize;
}
callCongestionControl = false; // No congestion control on cWnd show be invoked
m_dupAckCount = SafeSubtraction (m_dupAckCount, segsAcked); // Update the dupAckCount
m_txBuffer->DiscardUpTo (ackNumber); //Bug 1850: retransmit before newack
DoRetransmit (); // Assume the next seq is lost. Retransmit lost packet
if (m_isFirstPartialAck)
{
m_isFirstPartialAck = false;
m_congestionControl->PktsAcked (m_tcb, segsAcked - oldDupAckCount, m_lastRtt);
}
else
{
resetRTO = false;
NS_ASSERT (oldDupAckCount - segsAcked == 1);
}
/* This partial ACK acknowledge the fact that one segment has been
* previously lost and now successfully received. All others have
* been processed when they come under the form of dupACKs
*/
m_congestionControl->PktsAcked (m_tcb, 1, m_lastRtt);
NS_LOG_INFO ("Partial ACK for seq " << ackNumber <<
" in fast recovery: cwnd set to " << m_tcb->m_cWnd <<
" recover seq: " << m_recover <<
" dupAck count: " << m_dupAckCount);
NS_LOG_DEBUG (segsAcked << " segments acked in CA_DISORDER, ack of " <<
ackNumber << " exiting CA_DISORDER -> CA_OPEN");
}
else if (ackNumber >= m_recover)
{ // Full ACK (RFC2582 sec.3 bullet #5 paragraph 2, option 1)
m_tcb->m_cWnd = std::min (m_tcb->m_ssThresh.Get (),
BytesInFlight () + m_tcb->m_segmentSize);
m_isFirstPartialAck = true;
m_dupAckCount = 0;
/* This FULL ACK acknowledge the fact that one segment has been
* previously lost and now successfully received. All others have
* been processed when they come under the form of dupACKs,
* except the (maybe) new ACKs which come from a new window
*/
m_congestionControl->PktsAcked (m_tcb, segsAcked, m_lastRtt);
newSegsAcked = (ackNumber - m_recover) / m_tcb->m_segmentSize;
m_congestionControl->CongestionStateSet (m_tcb, TcpSocketState::CA_OPEN);
m_tcb->m_congState = TcpSocketState::CA_OPEN;
NS_LOG_INFO ("Received full ACK for seq " << ackNumber <<
". Leaving fast recovery with cwnd set to " << m_tcb->m_cWnd);
NS_LOG_DEBUG ("RECOVERY -> OPEN");
}
}
else if (m_tcb->m_congState == TcpSocketState::CA_LOSS)
{
// Go back in OPEN state
m_isFirstPartialAck = true;
m_congestionControl->PktsAcked (m_tcb, segsAcked, m_lastRtt);
m_dupAckCount = 0;
if(ackNumber >= m_recover + 1)
// RFC 6675, Section 5:
// Once a TCP is in the loss recovery phase, the following procedure
// MUST be used for each arriving ACK:
// (A) An incoming cumulative ACK for a sequence number greater than
// RecoveryPoint signals the end of loss recovery, and the loss
// recovery phase MUST be terminated. Any information contained in
// the scoreboard for sequence numbers greater than the new value of
// HighACK SHOULD NOT be cleared when leaving the loss recovery
// phase.
else if (m_tcb->m_congState == TcpSocketState::CA_RECOVERY)
{
m_isFirstPartialAck = true;
// Recalculate the segs acked, that are from m_recover to ackNumber
// (which are the ones we have not passed to PktsAcked and that
// can increase cWnd)
segsAcked = (ackNumber - m_recover) / m_tcb->m_segmentSize;
m_congestionControl->PktsAcked (m_tcb, segsAcked, m_lastRtt);
m_congestionControl->CongestionStateSet (m_tcb, TcpSocketState::CA_OPEN);
m_tcb->m_congState = TcpSocketState::CA_OPEN;
NS_LOG_DEBUG ("LOSS -> OPEN");
}
m_congestionControl->CongestionStateSet (m_tcb, TcpSocketState::CA_OPEN);
m_tcb->m_congState = TcpSocketState::CA_OPEN;
NS_LOG_DEBUG ("LOSS -> OPEN");
}
if (callCongestionControl)
{
m_congestionControl->IncreaseWindow (m_tcb, newSegsAcked);
NS_LOG_DEBUG (segsAcked << " segments acked in CA_RECOVER, ack of " <<
ackNumber << ", exiting CA_RECOVERY -> CA_OPEN");
}
else if (m_tcb->m_congState == TcpSocketState::CA_LOSS)
{
m_isFirstPartialAck = true;
// Recalculate the segs acked, that are from m_recover to ackNumber
// (which are the ones we have not passed to PktsAcked and that
// can increase cWnd)
segsAcked = (ackNumber - m_recover) / m_tcb->m_segmentSize;
m_congestionControl->PktsAcked (m_tcb, segsAcked, m_lastRtt);
m_congestionControl->CongestionStateSet (m_tcb, TcpSocketState::CA_OPEN);
m_tcb->m_congState = TcpSocketState::CA_OPEN;
NS_LOG_DEBUG (segsAcked << " segments acked in CA_LOSS, ack of" <<
ackNumber << ", exiting CA_LOSS -> CA_OPEN");
}
m_congestionControl->IncreaseWindow (m_tcb, segsAcked);
m_dupAckCount = 0;
NS_LOG_LOGIC ("Congestion control called: " <<
" cWnd: " << m_tcb->m_cWnd <<
" ssTh: " << m_tcb->m_ssThresh);
NewAck (ackNumber, true);
}
// Reset the data retransmission count. We got a new ACK!
m_dataRetrCount = m_dataRetries;
if (m_isFirstPartialAck == false)
{
NS_ASSERT (m_tcb->m_congState == TcpSocketState::CA_RECOVERY);
}
NewAck (ackNumber, resetRTO);
// Try to send more data
if (!m_sendPendingDataEvent.IsRunning ())
{
m_sendPendingDataEvent = Simulator::Schedule (TimeStep (1),
&TcpSocketBase::SendPendingData,
this, m_connected);
}
}
// If there is any data piggybacked, store it into m_rxBuffer
if (packet->GetSize () > 0)
{
ReceivedData (packet, tcpHeader);
}
}
@@ -3019,6 +3095,9 @@ TcpSocketBase::NewAck (SequenceNumber32 const& ack, bool resetRTO)
{
NS_LOG_FUNCTION (this << ack);
// Reset the data retransmission count. We got a new ACK!
m_dataRetrCount = m_dataRetries;
if (m_state != SYN_RCVD && resetRTO)
{ // Set RTO unless the ACK is received in SYN_RCVD state
NS_LOG_LOGIC (this << " Cancelled ReTxTimeout event which was set to expire at " <<

View File

@@ -250,10 +250,11 @@ public:
* RFC 3042.
*
* In ns-3, these algorithms are included in this class, and it is implemented inside
* the ReceivedAck method. The attribute which manages the number of dup ACKs
* the ProcessAck method. The attribute which manages the number of dup ACKs
* necessary to start the fast retransmit algorithm is named "ReTxThreshold",
* and its default value is 3, while the Limited Transmit one can be enabled
* by setting the attribute "LimitedTransmit" to true.
* by setting the attribute "LimitedTransmit" to true. Before entering the
* recovery phase, the method EnterRecovery is called.
*
* Fast recovery
* --------------------------
@@ -263,7 +264,7 @@ public:
* the slow start threshold is halved, and the cWnd is set equal to such value,
* plus segments for the cWnd inflation.
*
* The algorithm is implemented in the ReceivedAck method.
* The algorithm is implemented in the ProcessAck method.
*
*/
class TcpSocketBase : public TcpSocket
@@ -838,6 +839,14 @@ protected:
*/
virtual void ReceivedAck (Ptr<Packet> packet, const TcpHeader& tcpHeader);
/**
* \brief Process a received ack
* \param ackNumber ack number
* \param scoreboardUpdated if true indicates that the scoreboard has been
* updated with SACK information
*/
virtual void ProcessAck (const SequenceNumber32 &ackNumber, bool scoreboardUpdated);
/**
* \brief Recv of a data, put into buffer, call L7 to get it if necessary
* \param packet the packet
@@ -880,9 +889,9 @@ protected:
void LimitedTransmit ();
/**
* \brief Enter the FastRetransmit, and retransmit the head
* \brief Enter the CA_RECOVERY, and retransmit the head
*/
void FastRetransmit ();
void EnterRecovery ();
/**
* \brief Call Retransmit() upon RTO event