diff --git a/src/internet/doc/tcp.rst b/src/internet/doc/tcp.rst index 1de27d188..462d61673 100644 --- a/src/internet/doc/tcp.rst +++ b/src/internet/doc/tcp.rst @@ -464,11 +464,11 @@ by (1/cwnd) with a rounding off due to type casting into int. if (m_cWndCnt >= w) { - uint32_t delta = m_cWndCnt / w; + uint32_t delta = m_cWndCnt / w; - m_cWndCnt -= delta * w; - tcb->m_cWnd += delta * tcb->m_segmentSize; - NS_LOG_DEBUG("Subtracting delta * w from m_cWndCnt " << delta * w); + m_cWndCnt -= delta * w; + tcb->m_cWnd += delta * tcb->m_segmentSize; + NS_LOG_DEBUG("Subtracting delta * w from m_cWndCnt " << delta * w); } @@ -476,13 +476,13 @@ by (1/cwnd) with a rounding off due to type casting into int. :caption: New Reno `cwnd` update if (segmentsAcked > 0) - { - double adder = static_cast(tcb->m_segmentSize * tcb->m_segmentSize) / tcb->m_cWnd.Get(); - adder = std::max(1.0, adder); - tcb->m_cWnd += static_cast(adder); - NS_LOG_INFO("In CongAvoid, updated to cwnd " << tcb->m_cWnd << + { + double adder = static_cast(tcb->m_segmentSize * tcb->m_segmentSize) / tcb->m_cWnd.Get(); + adder = std::max(1.0, adder); + tcb->m_cWnd += static_cast(adder); + NS_LOG_INFO("In CongAvoid, updated to cwnd " << tcb->m_cWnd << " ssthresh " << tcb->m_ssThresh); - } + } So, there are two main difference between the TCP Linux Reno and TCP NewReno @@ -844,11 +844,11 @@ As a first approximation, the LEDBAT sender operates as shown below: On receipt of an ACK:: - currentdelay = acknowledgement.delay - basedelay = min(basedelay, currentdelay) - queuingdelay = currentdelay - basedelay - offtarget =(TARGET - queuingdelay) / TARGET - cWnd += GAIN * offtarget * bytesnewlyacked * MSS / cWnd + currentdelay = acknowledgement.delay; + basedelay = min(basedelay, currentdelay); + queuingdelay = currentdelay - basedelay; + offtarget =(TARGET - queuingdelay) / TARGET; + cWnd += GAIN * offtarget * bytesnewlyacked * MSS / cWnd; ``TARGET`` is the maximum queueing delay that LEDBAT itself may introduce in the network, and ``GAIN`` determines the rate at which the cwnd responds to changes in @@ -899,14 +899,14 @@ On receipt of an ACK: .. math:: - One way delay = Receiver timestamp - Receiver timestamp echo reply - Smoothed one way delay = 7/8 * Old Smoothed one way delay + 1/8 * one way delay - If smoothed one way delay > owdMin + 15 * (owdMax - owdMin) / 100 - if LP_WITHIN_INF - cwnd = 1 - else - cwnd = cwnd / 2 - Inference timer is set + \text{One way delay} &= \text{Receiver timestamp} - \text{Receiver timestamp echo reply} \\ + \text{Smoothed one way delay} &= \frac{7}{8} \times \text{Old Smoothed one way delay} + \frac{1}{8} \times \text{one way delay} \\ + \text{If smoothed one way delay} &> \text{owdMin} + \frac{15 \times (\text{owdMax} - \text{owdMin})}{100} \\ + &\text{if LP\_WITHIN\_INF} \\ + &\quad \text{cwnd} = 1 \\ + &\text{else} \\ + &\quad \text{cwnd} = \frac{\text{cwnd}}{2} \\ + &\text{Inference timer is set} where owdMin and owdMax are the minimum and maximum one way delays experienced throughout the connection, LP_WITHIN_INF indicates if TCP-LP is in inference @@ -1056,31 +1056,36 @@ limiting the rate at which packets are sent. It caps the cwnd to one BDP and paces out packets at a rate which is adjusted based on the latest estimate of delivery rate. BBR algorithm is agnostic to packet losses and ECN marks. -pacing_gain controls the rate of sending data and cwnd_gain controls the amount +``pacing_gain`` controls the rate of sending data and ``cwnd_gain`` controls the amount of data to send. The following is a high level overview of BBR congestion control algorithm: On receiving an ACK:: - rtt = now - packet.sent_time - update_minimum_rtt(rtt) - delivery_rate = estimate_delivery_rate(packet) - update_maximum_bandwidth(delivery_rate) + rtt = now - packet.sent_time; + update_minimum_rtt(rtt); + delivery_rate = estimate_delivery_rate(packet); + update_maximum_bandwidth(delivery_rate); After transmitting a data packet:: - bdp = max_bandwidth * min_rtt - if (cwnd * bdp < inflight) - return - if (now > nextSendTime) - { - transmit(packet) - nextSendTime = now + packet.size /(pacing_gain * max_bandwidth) - } - else - return - Schedule(nextSendTime, Send) + bdp = max_bandwidth * min_rtt; + if (cwnd * bdp < inflight) + { + return; + } + if (now > nextSendTime) + { + transmit(packet); + nextSendTime = now + packet.size / (pacing_gain * max_bandwidth); + } + else + { + return; + } + Schedule(nextSendTime, Send); + To enable BBR on all TCP sockets, the following configuration can be used:: @@ -1099,10 +1104,10 @@ In addition, the following unit tests have been written to validate the implementation of BBR in ns-3: * BBR should enable (if not already done) TCP pacing feature. -* Test to validate the values of pacing_gain and cwnd_gain in different phases +* Test to validate the values of ``pacing_gain`` and ``cwnd_gain`` in different phases of BBR. -An example program, examples/tcp/tcp-bbr-example.cc, is provided to experiment +An example program, ``examples/tcp/tcp-bbr-example.cc``, is provided to experiment with BBR for one long running flow. This example uses a simple topology consisting of one sender, one receiver and two routers to examine congestion window, throughput and queue control. A program similar to this has been run @@ -1190,43 +1195,43 @@ ECN capability is negotiated during the three-way TCP handshake: :: - if (m_useEcn == UseEcn_t::On) - { - SendEmptyPacket(TcpHeader::SYN | TcpHeader::ECE | TcpHeader::CWR); - } - else - { - SendEmptyPacket(TcpHeader::SYN); - } - m_ecnState = ECN_DISABLED; + if (m_useEcn == UseEcn_t::On) + { + SendEmptyPacket(TcpHeader::SYN | TcpHeader::ECE | TcpHeader::CWR); + } + else + { + SendEmptyPacket(TcpHeader::SYN); + } + m_ecnState = ECN_DISABLED; 2. Receiver sends SYN + ACK + ECE :: - if (m_useEcn != UseEcn_t::Off &&(tcpHeader.GetFlags() &(TcpHeader::CWR | TcpHeader::ECE)) == (TcpHeader::CWR | TcpHeader::ECE)) - { - SendEmptyPacket(TcpHeader::SYN | TcpHeader::ACK |TcpHeader::ECE); - m_ecnState = ECN_IDLE; - } - else - { - SendEmptyPacket(TcpHeader::SYN | TcpHeader::ACK); - m_ecnState = ECN_DISABLED; - } + if (m_useEcn != UseEcn_t::Off &&(tcpHeader.GetFlags() &(TcpHeader::CWR | TcpHeader::ECE)) == (TcpHeader::CWR | TcpHeader::ECE)) + { + SendEmptyPacket(TcpHeader::SYN | TcpHeader::ACK |TcpHeader::ECE); + m_ecnState = ECN_IDLE; + } + else + { + SendEmptyPacket(TcpHeader::SYN | TcpHeader::ACK); + m_ecnState = ECN_DISABLED; + } 3. Sender sends ACK :: - if (m_useEcn != UseEcn_t::Off && (tcpHeader.GetFlags() &(TcpHeader::CWR | TcpHeader::ECE)) == (TcpHeader::ECE)) - { - m_ecnState = ECN_IDLE; - } - else - { - m_ecnState = ECN_DISABLED; - } + if (m_useEcn != UseEcn_t::Off && (tcpHeader.GetFlags() &(TcpHeader::CWR | TcpHeader::ECE)) == (TcpHeader::ECE)) + { + m_ecnState = ECN_IDLE; + } + else + { + m_ecnState = ECN_DISABLED; + } Once the ECN-negotiation is successful, the sender sends data packets with ECT bits set in the IP header. @@ -1779,10 +1784,10 @@ The code is the following: :: - TcpZeroWindowTest::TcpZeroWindowTest(const std::string &desc) - : TcpGeneralTest(desc) - { - } + TcpZeroWindowTest::TcpZeroWindowTest(const std::string &desc) + : TcpGeneralTest(desc) + { + } Then, one should define the general parameters for the TCP connection, which will be one-sided (one node is acting as SENDER, while the other is acting as @@ -1808,15 +1813,15 @@ the method ConfigureEnvironment: :: - void - TcpZeroWindowTest::ConfigureEnvironment() - { - TcpGeneralTest::ConfigureEnvironment(); - SetAppPktCount(20); - SetMTU(500); - SetTransmitStart(Seconds(2.0)); - SetPropagationDelay(MilliSeconds(50)); - } + void + TcpZeroWindowTest::ConfigureEnvironment() + { + TcpGeneralTest::ConfigureEnvironment(); + SetAppPktCount(20); + SetMTU(500); + SetTransmitStart(Seconds(2.0)); + SetPropagationDelay(MilliSeconds(50)); + } For other properties, set after the object creation, one can use ConfigureProperties (). @@ -1828,12 +1833,12 @@ documentation for an exhaustive list of the tunable properties. :: - void - TcpZeroWindowTest::ConfigureProperties() - { - TcpGeneralTest::ConfigureProperties(); - SetInitialCwnd(SENDER, 10); - } + void + TcpZeroWindowTest::ConfigureProperties() + { + TcpGeneralTest::ConfigureProperties(); + SetInitialCwnd(SENDER, 10); + } To see the default value for the experiment, please see the implementation of both methods inside TcpGeneralTest class. @@ -1857,13 +1862,13 @@ following code): Ptr TcpZeroWindowTest::CreateReceiverSocket(Ptr node) { - Ptr socket = TcpGeneralTest::CreateReceiverSocket(node); + Ptr socket = TcpGeneralTest::CreateReceiverSocket(node); - socket->SetAttribute("RcvBufSize", UintegerValue(0)); - Simulator::Schedule(Seconds(10.0), + socket->SetAttribute("RcvBufSize", UintegerValue(0)); + Simulator::Schedule(Seconds(10.0), &TcpZeroWindowTest::IncreaseBufSize, this); - return socket; + return socket; } Even so, to check the active window update, we should schedule an increase @@ -1872,11 +1877,11 @@ IncreaseBufSize. :: - void - TcpZeroWindowTest::IncreaseBufSize() - { - SetRcvBufSize(RECEIVER, 2500); - } + void + TcpZeroWindowTest::IncreaseBufSize() + { + SetRcvBufSize(RECEIVER, 2500); + } Which utilizes the SetRcvBufSize method to edit the RxBuffer object of the RECEIVER. As said before, check the Doxygen documentation for class TcpGeneralTest @@ -1897,17 +1902,17 @@ to be aware of the various possibilities that it offers. TcpGeneralTest::SetRcvBufSize(SocketWho who, uint32_t size) { if (who == SENDER) - { + { m_senderSocket->SetRcvBufSize(size); - } + } else if (who == RECEIVER) - { + { m_receiverSocket->SetRcvBufSize(size); - } + } else - { + { NS_FATAL_ERROR("Not defined"); - } + } } Next, we can start to follow the TCP connection: @@ -1929,22 +1934,22 @@ the first SYN-ACK has 0 as advertised window size: :: - void - TcpZeroWindowTest::Tx(const Ptr p, const TcpHeader &h, SocketWho who) - { - ... - else if (who == RECEIVER) - { - NS_LOG_INFO("\tRECEIVER TX " << h << " size " << p->GetSize()); + void + TcpZeroWindowTest::Tx(const Ptr p, const TcpHeader &h, SocketWho who) + { + ... + else if (who == RECEIVER) + { + NS_LOG_INFO("\tRECEIVER TX " << h << " size " << p->GetSize()); - if (h.GetFlags() & TcpHeader::SYN) - { - NS_TEST_ASSERT_MSG_EQ(h.GetWindowSize(), 0, - "RECEIVER window size is not 0 in the SYN-ACK"); - } - } - .... - } + if (h.GetFlags() & TcpHeader::SYN) + { + NS_TEST_ASSERT_MSG_EQ(h.GetWindowSize(), 0, + "RECEIVER window size is not 0 in the SYN-ACK"); + } + } + .... + } Practically, we are checking that every SYN packet sent by the RECEIVER has the advertised window set to 0. The same thing is done also by checking, in the Rx @@ -1977,20 +1982,20 @@ processing of the SYN-ACK: :: - void - TcpZeroWindowTest::ProcessedAck(const Ptr tcb, - const TcpHeader& h, SocketWho who) - { - if (who == SENDER) - { - if (h.GetFlags() & TcpHeader::SYN) - { - EventId persistentEvent = GetPersistentEvent(SENDER); - NS_TEST_ASSERT_MSG_EQ(persistentEvent.IsPending(), true, + void + TcpZeroWindowTest::ProcessedAck(const Ptr tcb, + const TcpHeader& h, SocketWho who) + { + if (who == SENDER) + { + if (h.GetFlags() & TcpHeader::SYN) + { + EventId persistentEvent = GetPersistentEvent(SENDER); + NS_TEST_ASSERT_MSG_EQ(persistentEvent.IsPending(), true, "Persistent event not started"); - } - } - } + } + } + } Since we programmed the increase of the buffer size after 10 simulated seconds, we expect the persistent timer to fire before any rWnd changes. When it fires, @@ -2003,21 +2008,21 @@ again a zero window situation. At first, we investigates on what the sender send if (Simulator::Now().GetSeconds() <= 6.0) { - NS_TEST_ASSERT_MSG_EQ(p->GetSize() - h.GetSerializedSize(), 0, + NS_TEST_ASSERT_MSG_EQ(p->GetSize() - h.GetSerializedSize(), 0, "Data packet sent anyway"); } else if (Simulator::Now().GetSeconds() > 6.0 && Simulator::Now().GetSeconds() <= 7.0) { - NS_TEST_ASSERT_MSG_EQ(m_zeroWindowProbe, false, "Sent another probe"); + NS_TEST_ASSERT_MSG_EQ(m_zeroWindowProbe, false, "Sent another probe"); - if (! m_zeroWindowProbe) + if (! m_zeroWindowProbe) { - NS_TEST_ASSERT_MSG_EQ(p->GetSize() - h.GetSerializedSize(), 1, - "Data packet sent instead of window probe"); - NS_TEST_ASSERT_MSG_EQ(h.GetSequenceNumber(), SequenceNumber32(1), - "Data packet sent instead of window probe"); - m_zeroWindowProbe = true; + NS_TEST_ASSERT_MSG_EQ(p->GetSize() - h.GetSerializedSize(), 1, + "Data packet sent instead of window probe"); + NS_TEST_ASSERT_MSG_EQ(h.GetSequenceNumber(), SequenceNumber32(1), + "Data packet sent instead of window probe"); + m_zeroWindowProbe = true; } } @@ -2034,14 +2039,14 @@ Only one probe is allowed, and this is the reason for the check at line 11. :linenos: :emphasize-lines: 6,7 - if (Simulator::Now().GetSeconds() > 6.0 && + if (Simulator::Now().GetSeconds() > 6.0 && Simulator::Now().GetSeconds() <= 7.0) - { - NS_TEST_ASSERT_MSG_EQ(h.GetSequenceNumber(), SequenceNumber32(1), + { + NS_TEST_ASSERT_MSG_EQ(h.GetSequenceNumber(), SequenceNumber32(1), "Data packet sent instead of window probe"); - NS_TEST_ASSERT_MSG_EQ(h.GetWindowSize(), 0, + NS_TEST_ASSERT_MSG_EQ(h.GetWindowSize(), 0, "No zero window advertised by RECEIVER"); - } + } For the RECEIVER, the interval between 6 and 7 seconds is when the zero-window segment is sent. @@ -2051,21 +2056,21 @@ exchange between the 7 and 10 seconds mark. :: - else if (Simulator::Now().GetSeconds() > 7.0 && - Simulator::Now().GetSeconds() < 10.0) - { - NS_FATAL_ERROR("No packets should be sent before the window update"); - } + else if (Simulator::Now().GetSeconds() > 7.0 && + Simulator::Now().GetSeconds() < 10.0) + { + NS_FATAL_ERROR("No packets should be sent before the window update"); + } The state checks are performed at the end of the methods, since they are valid in every condition: :: - NS_TEST_ASSERT_MSG_EQ(GetCongStateFrom(GetTcb(SENDER)), TcpSocketState::CA_OPEN, - "Sender State is not OPEN"); - NS_TEST_ASSERT_MSG_EQ(GetCongStateFrom(GetTcb(RECEIVER)), TcpSocketState::CA_OPEN, - "Receiver State is not OPEN"); + NS_TEST_ASSERT_MSG_EQ(GetCongStateFrom(GetTcb(SENDER)), TcpSocketState::CA_OPEN, + "Sender State is not OPEN"); + NS_TEST_ASSERT_MSG_EQ(GetCongStateFrom(GetTcb(RECEIVER)), TcpSocketState::CA_OPEN, + "Receiver State is not OPEN"); Now, the interesting part in the Tx method is to check that after the 10.0 seconds mark (when the RECEIVER sends the active window update) the value of @@ -2073,11 +2078,11 @@ the window should be greater than zero (and precisely, set to 2500): :: - else if (Simulator::Now().GetSeconds() >= 10.0) - { - NS_TEST_ASSERT_MSG_EQ(h.GetWindowSize(), 2500, - "Receiver window not updated"); - } + else if (Simulator::Now().GetSeconds() >= 10.0) + { + NS_TEST_ASSERT_MSG_EQ(h.GetWindowSize(), 2500, + "Receiver window not updated"); + } To be sure that the sender receives the window update, we can use the Rx method: @@ -2102,18 +2107,18 @@ the connection ends with a success: :: - void - TcpZeroWindowTest::NormalClose(SocketWho who) - { - if (who == SENDER) - { - m_senderFinished = true; - } - else if (who == RECEIVER) - { - m_receiverFinished = true; - } - } + void + TcpZeroWindowTest::NormalClose(SocketWho who) + { + if (who == SENDER) + { + m_senderFinished = true; + } + else if (who == RECEIVER) + { + m_receiverFinished = true; + } + } The method is called only if all bytes are transmitted successfully. Then, in the method FinalChecks(), we check all variables, which should be true (which @@ -2121,18 +2126,18 @@ indicates that we have perfectly closed the connection). :: - void - TcpZeroWindowTest::FinalChecks() - { - NS_TEST_ASSERT_MSG_EQ(m_zeroWindowProbe, true, + void + TcpZeroWindowTest::FinalChecks() + { + NS_TEST_ASSERT_MSG_EQ(m_zeroWindowProbe, true, "Zero window probe not sent"); - NS_TEST_ASSERT_MSG_EQ(m_windowUpdated, true, + NS_TEST_ASSERT_MSG_EQ(m_windowUpdated, true, "Window has not updated during the connection"); - NS_TEST_ASSERT_MSG_EQ(m_senderFinished, true, + NS_TEST_ASSERT_MSG_EQ(m_senderFinished, true, "Connection not closed successfully(SENDER)"); - NS_TEST_ASSERT_MSG_EQ(m_receiverFinished, true, + NS_TEST_ASSERT_MSG_EQ(m_receiverFinished, true, "Connection not closed successfully(RECEIVER)"); - } + } To run the test, the usual way is