tcp: Added RFC6675 NextSeg() into TcpTxBuffer
This commit is contained in:
@@ -740,6 +740,94 @@ TcpTxBuffer::IsLost (const SequenceNumber32 &seq, uint32_t dupThresh,
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
TcpTxBuffer::NextSeg (SequenceNumber32 *seq, uint32_t dupThresh,
|
||||
uint32_t segmentSize, bool isRecovery) const
|
||||
{
|
||||
NS_LOG_FUNCTION (this);
|
||||
|
||||
/* RFC 6675, NextSeg definition.
|
||||
*
|
||||
* (1) If there exists a smallest unSACKed sequence number 'S2' that
|
||||
* meets the following three criteria for determining loss, the
|
||||
* sequence range of one segment of up to SMSS octets starting
|
||||
* with S2 MUST be returned.
|
||||
*
|
||||
* (1.a) S2 is greater than HighRxt.
|
||||
*
|
||||
* (1.b) S2 is less than the highest octet covered by any
|
||||
* received SACK.
|
||||
*
|
||||
* (1.c) IsLost (S2) returns true.
|
||||
*/
|
||||
PacketList::const_iterator it;
|
||||
TcpTxItem *item;
|
||||
SequenceNumber32 seqPerRule3;
|
||||
bool isSeqPerRule3Valid = false;
|
||||
SequenceNumber32 beginOfCurrentPkt = m_firstByteSeq;
|
||||
|
||||
for (it = m_sentList.begin (); it != m_sentList.end (); ++it)
|
||||
{
|
||||
item = *it;
|
||||
|
||||
// Condition 1.a , 1.b , and 1.c
|
||||
if (item->m_retrans == false && item->m_sacked == false)
|
||||
{
|
||||
if (IsLost (beginOfCurrentPkt, it, dupThresh, segmentSize))
|
||||
{
|
||||
*seq = beginOfCurrentPkt;
|
||||
return true;
|
||||
}
|
||||
else if (seqPerRule3.GetValue () == 0 && isRecovery)
|
||||
{
|
||||
isSeqPerRule3Valid = true;
|
||||
seqPerRule3 = beginOfCurrentPkt;
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing found, iterate
|
||||
beginOfCurrentPkt += item->m_packet->GetSize ();
|
||||
}
|
||||
|
||||
/* (2) If no sequence number 'S2' per rule (1) exists but there
|
||||
* exists available unsent data and the receiver's advertised
|
||||
* window allows, the sequence range of one segment of up to SMSS
|
||||
* octets of previously unsent data starting with sequence number
|
||||
* HighData+1 MUST be returned.
|
||||
*/
|
||||
if (SizeFromSequence (m_firstByteSeq + m_sentSize) > 0)
|
||||
{
|
||||
*seq = m_firstByteSeq + m_sentSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* (3) If the conditions for rules (1) and (2) fail, but there exists
|
||||
* an unSACKed sequence number 'S3' that meets the criteria for
|
||||
* detecting loss given in steps (1.a) and (1.b) above
|
||||
* (specifically excluding step (1.c)), then one segment of up to
|
||||
* SMSS octets starting with S3 SHOULD be returned.
|
||||
*/
|
||||
if (isSeqPerRule3Valid)
|
||||
{
|
||||
*seq = seqPerRule3;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* (4) If the conditions for (1), (2), and (3) fail, but there exists
|
||||
* outstanding unSACKed data, we provide the opportunity for a
|
||||
* single "rescue" retransmission per entry into loss recovery.
|
||||
* If HighACK is greater than RescueRxt (or RescueRxt is
|
||||
* undefined), then one segment of up to SMSS octets that MUST
|
||||
* include the highest outstanding unSACKed sequence number
|
||||
* SHOULD be returned, and RescueRxt set to RecoveryPoint.
|
||||
* HighRxt MUST NOT be updated.
|
||||
*
|
||||
* This point require too much interaction between us and TcpSocketBase.
|
||||
* We choose to not respect the SHOULD (allowed from RFC MUST/SHOULD definition)
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ostream &
|
||||
operator<< (std::ostream & os, TcpTxBuffer const & tcpTxBuf)
|
||||
{
|
||||
|
||||
@@ -223,6 +223,18 @@ public:
|
||||
*/
|
||||
bool IsLost (const SequenceNumber32 &seq, uint32_t dupThresh, uint32_t segmentSize) const;
|
||||
|
||||
/**
|
||||
* \brief Get the next sequence number to transmit, according to RFC 6675
|
||||
*
|
||||
* \param seq Next sequence number to transmit, based on the scoreboard information
|
||||
* \param dupThresh dupAck threshold
|
||||
* \param segmentSize segment size
|
||||
* \param isRecovery true if the socket congestion state is in recovery mode
|
||||
* \return true is seq is updated, false otherwise
|
||||
*/
|
||||
bool NextSeg (SequenceNumber32 *seq, uint32_t dupThresh, uint32_t segmentSize,
|
||||
bool isRecovery) const;
|
||||
|
||||
private:
|
||||
friend std::ostream & operator<< (std::ostream & os, TcpTxBuffer const & tcpTxBuf);
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ private:
|
||||
|
||||
void TestNewBlock ();
|
||||
void TestTransmittedBlock ();
|
||||
void TestNextSeg ();
|
||||
};
|
||||
|
||||
TcpTxBufferTestCase::TcpTxBufferTestCase ()
|
||||
@@ -65,10 +66,144 @@ TcpTxBufferTestCase::DoRun ()
|
||||
*/
|
||||
Simulator::Schedule (Seconds (0.0),
|
||||
&TcpTxBufferTestCase::TestTransmittedBlock, this);
|
||||
Simulator::Schedule (Seconds (0.0),
|
||||
&TcpTxBufferTestCase::TestNextSeg, this);
|
||||
|
||||
Simulator::Run ();
|
||||
Simulator::Destroy ();
|
||||
}
|
||||
|
||||
void
|
||||
TcpTxBufferTestCase::TestNextSeg ()
|
||||
{
|
||||
TcpTxBuffer txBuf;
|
||||
SequenceNumber32 head (1);
|
||||
SequenceNumber32 ret;
|
||||
uint32_t dupThresh = 3;
|
||||
uint32_t segmentSize = 150;
|
||||
Ptr<TcpOptionSack> sack = CreateObject<TcpOptionSack> ();
|
||||
|
||||
// At the beginning the values of dupThresh and segmentSize don't matter
|
||||
NS_TEST_ASSERT_MSG_EQ (txBuf.NextSeg (&ret, 0, 0, false), false,
|
||||
"NextSeq should not be returned at the beginning");
|
||||
|
||||
txBuf.SetHeadSequence (head);
|
||||
NS_TEST_ASSERT_MSG_EQ (txBuf.NextSeg (&ret, 0, 0, false), false,
|
||||
"NextSeq should not be returned with no data");
|
||||
|
||||
// Add a single, 3000-bytes long, packet
|
||||
txBuf.Add (Create<Packet> (30000));
|
||||
NS_TEST_ASSERT_MSG_EQ (txBuf.NextSeg (&ret, 0, 0, false), true,
|
||||
"No NextSeq with data at beginning");
|
||||
NS_TEST_ASSERT_MSG_EQ (ret.GetValue (), head.GetValue (),
|
||||
"Different NextSeq than expected at the beginning");
|
||||
|
||||
// Simulate sending 100 packets, 150 bytes long each, from seq 1
|
||||
for (uint32_t i=0; i<100; ++i)
|
||||
{
|
||||
NS_TEST_ASSERT_MSG_EQ (txBuf.NextSeg (&ret, dupThresh, segmentSize, false), true,
|
||||
"No NextSeq with data while \"transmitting\"");
|
||||
NS_TEST_ASSERT_MSG_EQ (ret, head + (segmentSize * i),
|
||||
"Different NextSeq than expected while \"transmitting\"");
|
||||
txBuf.CopyFromSequence (segmentSize, ret);
|
||||
}
|
||||
|
||||
// Ok, now simulate we lost the first segment [1;151], and that we have
|
||||
// limited transmit. NextSeg should return (up to dupThresh-1) new pieces of data
|
||||
SequenceNumber32 lastRet = ret; // This is like m_highTx
|
||||
for (uint32_t i=1; i<dupThresh; ++i) // iterate dupThresh-1 times (limited transmit)
|
||||
{
|
||||
SequenceNumber32 begin = head + (segmentSize * i);
|
||||
SequenceNumber32 end = begin + segmentSize;
|
||||
sack->AddSackBlock (TcpOptionSack::SackBlock (begin, end));
|
||||
txBuf.Update (sack->GetSackList ());
|
||||
|
||||
// new data expected and sent
|
||||
NS_TEST_ASSERT_MSG_EQ (txBuf.NextSeg (&ret, dupThresh, segmentSize, false), true,
|
||||
"No NextSeq with SACK block while \"transmitting\"");
|
||||
NS_TEST_ASSERT_MSG_EQ (ret, lastRet + segmentSize,
|
||||
"Different NextSeq than expected in limited transmit");
|
||||
txBuf.CopyFromSequence (segmentSize, ret);
|
||||
sack->ClearSackList ();
|
||||
lastRet = ret;
|
||||
}
|
||||
|
||||
// Limited transmit was ok; now there is the dupThresh-th dupack.
|
||||
// Now we need to retransmit the first block..
|
||||
sack->AddSackBlock (TcpOptionSack::SackBlock (head + (segmentSize * (dupThresh)),
|
||||
head + (segmentSize * (dupThresh)) + segmentSize));
|
||||
txBuf.Update (sack->GetSackList ());
|
||||
NS_TEST_ASSERT_MSG_EQ (txBuf.NextSeg (&ret, dupThresh, segmentSize, false), true,
|
||||
"No NextSeq with SACK block for Fast Recovery");
|
||||
NS_TEST_ASSERT_MSG_EQ (ret, head,
|
||||
"Different NextSeq than expected for Fast Recovery");
|
||||
txBuf.CopyFromSequence (segmentSize, ret);
|
||||
sack->ClearSackList ();
|
||||
|
||||
// Fast Retransmission was ok; now check some additional dupacks.
|
||||
for (uint32_t i=1; i<=4; ++i)
|
||||
{
|
||||
sack->AddSackBlock (TcpOptionSack::SackBlock (head + (segmentSize * (dupThresh+i)),
|
||||
head + (segmentSize * (dupThresh+i)) + segmentSize));
|
||||
txBuf.Update (sack->GetSackList ());
|
||||
NS_TEST_ASSERT_MSG_EQ (txBuf.NextSeg (&ret, dupThresh, segmentSize, false), true,
|
||||
"No NextSeq with SACK block after recv dupacks in FR");
|
||||
NS_TEST_ASSERT_MSG_EQ (ret, lastRet + segmentSize,
|
||||
"Different NextSeq than expected after recv dupacks in FR");
|
||||
txBuf.CopyFromSequence (segmentSize, ret);
|
||||
sack->ClearSackList ();
|
||||
lastRet = ret;
|
||||
}
|
||||
|
||||
// Well now we receive a partial ACK, corresponding to the segment we retransmitted.
|
||||
// Unfortunately, the next one is lost as well; but NextSeg should be smart enough
|
||||
// to give us the next segment (head + segmentSize) to retransmit.
|
||||
/* In this particular case, we are checking the fact that we have badly crafted
|
||||
* the SACK blocks. Talking in segment, we transmitted 1,2,3,4,5 ... and then
|
||||
* received dupack for 1. While receiving these, we crafted SACK block in the
|
||||
* way that 2,3,4,... were correctly received. Now, if we receive an ACK for 2,
|
||||
* we clearly crafted the corresponding ACK wrongly. TcpTxBuffer should be able
|
||||
* to "backoff" that flag on its HEAD (segment 2). We still don't know for segment
|
||||
* 3,4 .. so keep them.
|
||||
*/
|
||||
head = head + segmentSize;
|
||||
txBuf.DiscardUpTo (head);
|
||||
NS_TEST_ASSERT_MSG_EQ (txBuf.NextSeg (&ret, dupThresh, segmentSize, false), true,
|
||||
"No NextSeq with SACK block after receiving partial ACK");
|
||||
NS_TEST_ASSERT_MSG_EQ (ret, head,
|
||||
"Different NextSeq than expected after receiving partial ACK");
|
||||
txBuf.CopyFromSequence (segmentSize, ret);
|
||||
|
||||
// Now, check for one more dupack...
|
||||
sack->AddSackBlock (TcpOptionSack::SackBlock (head + (segmentSize * (dupThresh+6)),
|
||||
head + (segmentSize * (dupThresh+6)) + segmentSize));
|
||||
txBuf.Update (sack->GetSackList ());
|
||||
NS_TEST_ASSERT_MSG_EQ (txBuf.NextSeg (&ret, dupThresh, segmentSize, false), true,
|
||||
"No NextSeq with SACK block after recv dupacks after partial ack");
|
||||
NS_TEST_ASSERT_MSG_EQ (ret, lastRet + segmentSize,
|
||||
"Different NextSeq than expected after recv dupacks after partial ack");
|
||||
txBuf.CopyFromSequence (segmentSize, ret);
|
||||
sack->ClearSackList ();
|
||||
head = lastRet = ret + segmentSize;
|
||||
|
||||
// And now ack everything we sent to date!
|
||||
txBuf.DiscardUpTo (head);
|
||||
|
||||
// And continue normally until the end
|
||||
for (uint32_t i=0; i<93; ++i)
|
||||
{
|
||||
NS_TEST_ASSERT_MSG_EQ (txBuf.NextSeg (&ret, dupThresh, segmentSize, false), true,
|
||||
"No NextSeq with data while \"transmitting\"");
|
||||
NS_TEST_ASSERT_MSG_EQ (ret, head + (segmentSize * i),
|
||||
"Different NextSeq than expected while \"transmitting\"");
|
||||
txBuf.CopyFromSequence (segmentSize, ret);
|
||||
}
|
||||
|
||||
txBuf.DiscardUpTo (ret+segmentSize);
|
||||
NS_TEST_ASSERT_MSG_EQ (txBuf.Size (), 0,
|
||||
"Data inside the buffer");
|
||||
}
|
||||
|
||||
void
|
||||
TcpTxBufferTestCase::TestNewBlock ()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user