From 9dcf3e62e2925c8b0e6775bfb4fb19e959460c92 Mon Sep 17 00:00:00 2001 From: Natale Patriciello Date: Fri, 3 Feb 2017 14:01:52 +0100 Subject: [PATCH] tcp: Added RFC6675 NextSeg() into TcpTxBuffer --- src/internet/model/tcp-tx-buffer.cc | 88 +++++++++++++++ src/internet/model/tcp-tx-buffer.h | 12 +++ src/internet/test/tcp-tx-buffer-test.cc | 135 ++++++++++++++++++++++++ 3 files changed, 235 insertions(+) diff --git a/src/internet/model/tcp-tx-buffer.cc b/src/internet/model/tcp-tx-buffer.cc index 636cc5c73..41da143f7 100644 --- a/src/internet/model/tcp-tx-buffer.cc +++ b/src/internet/model/tcp-tx-buffer.cc @@ -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) { diff --git a/src/internet/model/tcp-tx-buffer.h b/src/internet/model/tcp-tx-buffer.h index a643d4972..f70d8dd77 100644 --- a/src/internet/model/tcp-tx-buffer.h +++ b/src/internet/model/tcp-tx-buffer.h @@ -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); diff --git a/src/internet/test/tcp-tx-buffer-test.cc b/src/internet/test/tcp-tx-buffer-test.cc index c6ae82c73..ea20cdace 100644 --- a/src/internet/test/tcp-tx-buffer-test.cc +++ b/src/internet/test/tcp-tx-buffer-test.cc @@ -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 sack = CreateObject (); + + // 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 (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; iAddSackBlock (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 () {