Nagle's algorithm in TCP (closes bug 1039)
authorAdrian S Tam <adrian.sw.tam@gmail.com>
Wed, 07 Dec 2011 11:22:10 -0500
changeset 7619 b4dee6307aa7
parent 7618 9b0902620ff7
child 7620 4da35aed3cb2
Nagle's algorithm in TCP (closes bug 1039)
src/internet/model/nsc-tcp-socket-impl.cc
src/internet/model/nsc-tcp-socket-impl.h
src/internet/model/rtt-estimator.cc
src/internet/model/tcp-socket-base.cc
src/internet/model/tcp-socket-base.h
src/internet/model/tcp-socket.cc
src/internet/model/tcp-socket.h
src/test/ns3tcp/ns3tcp-no-delay-test-suite.cc
src/test/ns3tcp/wscript
--- a/src/internet/model/nsc-tcp-socket-impl.cc	Tue Dec 06 08:42:44 2011 -0500
+++ b/src/internet/model/nsc-tcp-socket-impl.cc	Wed Dec 07 11:22:10 2011 -0500
@@ -85,6 +85,7 @@
   : TcpSocket (sock), //copy the base class callbacks
     m_delAckMaxCount (sock.m_delAckMaxCount),
     m_delAckTimeout (sock.m_delAckTimeout),
+    m_noDelay (sock.m_noDelay),
     m_endPoint (0),
     m_node (sock.m_node),
     m_tcp (sock.m_tcp),
@@ -797,6 +798,18 @@
   return m_delAckMaxCount;
 }
 
+void
+NscTcpSocketImpl::SetTcpNoDelay (bool noDelay)
+{
+  m_noDelay = noDelay;
+}
+
+bool
+NscTcpSocketImpl::GetTcpNoDelay (void) const
+{
+  return m_noDelay;
+}
+
 void 
 NscTcpSocketImpl::SetPersistTimeout (Time timeout)
 {
--- a/src/internet/model/nsc-tcp-socket-impl.h	Tue Dec 06 08:42:44 2011 -0500
+++ b/src/internet/model/nsc-tcp-socket-impl.h	Wed Dec 07 11:22:10 2011 -0500
@@ -125,12 +125,15 @@
   virtual Time GetDelAckTimeout (void) const;
   virtual void SetDelAckMaxCount (uint32_t count);
   virtual uint32_t GetDelAckMaxCount (void) const;
+  virtual void SetTcpNoDelay (bool noDelay);
+  virtual bool GetTcpNoDelay (void) const;
   virtual void SetPersistTimeout (Time timeout);
   virtual Time GetPersistTimeout (void) const;
 
   enum Socket::SocketErrno GetNativeNs3Errno (int err) const;
   uint32_t m_delAckMaxCount;
   Time m_delAckTimeout;
+  bool m_noDelay;
 
   Ipv4EndPoint *m_endPoint;
   Ptr<Node> m_node;
--- a/src/internet/model/rtt-estimator.cc	Tue Dec 06 08:42:44 2011 -0500
+++ b/src/internet/model/rtt-estimator.cc	Wed Dec 07 11:22:10 2011 -0500
@@ -54,7 +54,7 @@
                    MakeTimeChecker ())
     .AddAttribute ("MinRTO", 
                    "Minimum retransmit timeout value",
-                   TimeValue (Seconds (0.2)),
+                   TimeValue (Seconds (0.2)), // RFC2988 says min RTO=1 sec, but Linux uses 200ms. See http://www.postel.org/pipermail/end2end-interest/2004-November/004402.html
                    MakeTimeAccessor (&RttEstimator::SetMinRto,
                                      &RttEstimator::GetMinRto),
                    MakeTimeChecker ())
--- a/src/internet/model/tcp-socket-base.cc	Tue Dec 06 08:42:44 2011 -0500
+++ b/src/internet/model/tcp-socket-base.cc	Wed Dec 07 11:22:10 2011 -0500
@@ -120,6 +120,7 @@
     m_dupAckCount (sock.m_dupAckCount),
     m_delAckCount (0),
     m_delAckMaxCount (sock.m_delAckMaxCount),
+    m_noDelay (sock.m_noDelay),
     m_cnRetries (sock.m_cnRetries),
     m_delAckTimeout (sock.m_delAckTimeout),
     m_persistTimeout (sock.m_persistTimeout),
@@ -1456,6 +1457,14 @@
         {
           break; // No more
         }
+      // Nagle's algorithm (RFC896): Hold off sending if there is unacked data
+      // in the buffer and the amount of data to send is less than one segment
+      if (!m_noDelay && UnAckDataCount () > 0 &&
+          m_txBuffer.SizeFromSequence (m_nextTxSequence) < m_segmentSize)
+        {
+          NS_LOG_LOGIC ("Invoking Nagle's algorithm. Wait to send.");
+          break;
+        }
       uint32_t s = std::min (w, m_segmentSize);  // Send no more than window
       uint32_t sz = SendDataPacket (m_nextTxSequence, s, withAck);
       nPacketsSent++;                             // Count sent this loop
@@ -1836,6 +1845,18 @@
 }
 
 void
+TcpSocketBase::SetTcpNoDelay (bool noDelay)
+{
+  m_noDelay = noDelay;
+}
+
+bool
+TcpSocketBase::GetTcpNoDelay (void) const
+{
+  return m_noDelay;
+}
+
+void
 TcpSocketBase::SetPersistTimeout (Time timeout)
 {
   m_persistTimeout = timeout;
--- a/src/internet/model/tcp-socket-base.h	Tue Dec 06 08:42:44 2011 -0500
+++ b/src/internet/model/tcp-socket-base.h	Wed Dec 07 11:22:10 2011 -0500
@@ -116,6 +116,8 @@
   virtual Time     GetDelAckTimeout (void) const;
   virtual void     SetDelAckMaxCount (uint32_t count);
   virtual uint32_t GetDelAckMaxCount (void) const;
+  virtual void     SetTcpNoDelay (bool noDelay);
+  virtual bool     GetTcpNoDelay (void) const;
   virtual void     SetPersistTimeout (Time timeout);
   virtual Time     GetPersistTimeout (void) const;
   virtual bool     SetAllowBroadcast (bool allowBroadcast);
@@ -189,6 +191,7 @@
   uint32_t          m_dupAckCount;     //< Dupack counter
   uint32_t          m_delAckCount;     //< Delayed ACK counter
   uint32_t          m_delAckMaxCount;  //< Number of packet to fire an ACK before delay timeout
+  bool              m_noDelay;         //< Set to true to disable Nagle's algorithm
   uint32_t          m_cnCount;         //< Count of remaining connection retries
   uint32_t          m_cnRetries;       //< Number of connection retries before giving up
   TracedValue<Time> m_rto;             //< Retransmit timeout
--- a/src/internet/model/tcp-socket.cc	Tue Dec 06 08:42:44 2011 -0500
+++ b/src/internet/model/tcp-socket.cc	Wed Dec 07 11:22:10 2011 -0500
@@ -22,6 +22,7 @@
 #include "ns3/log.h"
 #include "ns3/uinteger.h"
 #include "ns3/double.h"
+#include "ns3/boolean.h"
 #include "ns3/trace-source-accessor.h"
 #include "ns3/nstime.h"
 #include "tcp-socket.h"
@@ -93,6 +94,11 @@
                    MakeUintegerAccessor (&TcpSocket::GetDelAckMaxCount,
                                          &TcpSocket::SetDelAckMaxCount),
                    MakeUintegerChecker<uint32_t> ())
+    .AddAttribute ("TcpNoDelay", "Set to true to disable Nagle's algorithm",
+                   BooleanValue (true),
+                   MakeBooleanAccessor (&TcpSocket::GetTcpNoDelay,
+                                        &TcpSocket::SetTcpNoDelay),
+                   MakeBooleanChecker ())
     .AddAttribute ("PersistTimeout",
                    "Persist timeout to probe for rx window",
                    TimeValue (Seconds (6)),
--- a/src/internet/model/tcp-socket.h	Tue Dec 06 08:42:44 2011 -0500
+++ b/src/internet/model/tcp-socket.h	Wed Dec 07 11:22:10 2011 -0500
@@ -90,6 +90,8 @@
   virtual Time GetDelAckTimeout (void) const = 0;
   virtual void SetDelAckMaxCount (uint32_t count) = 0;
   virtual uint32_t GetDelAckMaxCount (void) const = 0;
+  virtual void SetTcpNoDelay (bool noDelay) = 0;
+  virtual bool GetTcpNoDelay (void) const = 0;
   virtual void SetPersistTimeout (Time timeout) = 0;
   virtual Time GetPersistTimeout (void) const = 0;
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/ns3tcp/ns3tcp-no-delay-test-suite.cc	Wed Dec 07 11:22:10 2011 -0500
@@ -0,0 +1,206 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "ns3/log.h"
+#include "ns3/abort.h"
+#include "ns3/test.h"
+#include "ns3/pcap-file.h"
+#include "ns3/config.h"
+#include "ns3/string.h"
+#include "ns3/uinteger.h"
+#include "ns3/boolean.h"
+#include "ns3/data-rate.h"
+#include "ns3/inet-socket-address.h"
+#include "ns3/point-to-point-helper.h"
+#include "ns3/csma-helper.h"
+#include "ns3/internet-stack-helper.h"
+#include "ns3/ipv4-global-routing-helper.h"
+#include "ns3/ipv4-address-helper.h"
+#include "ns3/packet-sink-helper.h"
+#include "ns3/tcp-socket-factory.h"
+#include "ns3/node-container.h"
+#include "ns3/simulator.h"
+#include "ns3tcp-socket-writer.h"
+
+using namespace ns3;
+
+NS_LOG_COMPONENT_DEFINE ("Ns3TcpNoDelayTest");
+
+// ===========================================================================
+// Tests of Nagle's algorithm and the TCP no delay option
+// ===========================================================================
+//
+//
+class Ns3TcpNoDelayTestCase : public TestCase
+{
+public:
+  Ns3TcpNoDelayTestCase (bool noDelay);
+  virtual ~Ns3TcpNoDelayTestCase () {}
+
+private:
+  virtual void DoRun (void);
+  bool m_noDelay;
+  bool m_writeResults;
+
+  void SinkRx (std::string path, Ptr<const Packet> p, const Address &address);
+
+  TestVectors<uint32_t> m_inputs;
+  TestVectors<uint32_t> m_responses;
+};
+
+Ns3TcpNoDelayTestCase::Ns3TcpNoDelayTestCase (bool noDelay)
+  : TestCase ("Check that ns-3 TCP Nagle's algorithm works correctly and that we can turn it off."),
+    m_noDelay (noDelay),
+    m_writeResults (false)
+{
+}
+
+void 
+Ns3TcpNoDelayTestCase::SinkRx (std::string path, Ptr<const Packet> p, const Address &address)
+{
+  m_responses.Add (p->GetSize ());
+}
+
+void
+Ns3TcpNoDelayTestCase::DoRun (void)
+{
+  uint16_t sinkPort = 50000;
+  double sinkStopTime = 8;  // sec; will trigger Socket::Close
+  double writerStopTime = 5;  // sec; will trigger Socket::Close
+  double simStopTime = 10;  // sec
+  Time sinkStopTimeObj = Seconds (sinkStopTime);
+  Time writerStopTimeObj = Seconds (writerStopTime);
+  Time simStopTimeObj= Seconds (simStopTime);
+
+  Ptr<Node> n0 = CreateObject<Node> ();
+  Ptr<Node> n1 = CreateObject<Node> ();
+
+  PointToPointHelper pointToPoint;
+  pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));
+  pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));
+
+  NetDeviceContainer devices;
+  devices = pointToPoint.Install (n0, n1);
+
+  InternetStackHelper internet;
+  internet.InstallAll ();
+
+  Ipv4AddressHelper address;
+  address.SetBase ("10.1.1.0", "255.255.255.252");
+  Ipv4InterfaceContainer ifContainer = address.Assign (devices);
+
+  Ptr<SocketWriter> socketWriter = CreateObject<SocketWriter> ();
+  Address sinkAddress (InetSocketAddress (ifContainer.GetAddress (1), sinkPort));
+  socketWriter->Setup (n0, sinkAddress);
+  n0->AddApplication (socketWriter);
+  socketWriter->SetStartTime (Seconds (0.));
+  socketWriter->SetStopTime (writerStopTimeObj);
+
+  PacketSinkHelper sink ("ns3::TcpSocketFactory",
+                         InetSocketAddress (Ipv4Address::GetAny (), sinkPort));
+  ApplicationContainer apps = sink.Install (n1);
+  // Start the sink application at time zero, and stop it at sinkStopTime
+  apps.Start (Seconds (0.0));
+  apps.Stop (sinkStopTimeObj);
+
+  Config::Connect ("/NodeList/*/ApplicationList/*/$ns3::PacketSink/Rx",
+                   MakeCallback (&Ns3TcpNoDelayTestCase::SinkRx, this));
+
+  // Enable or disable TCP no delay option
+  Config::SetDefault ("ns3::TcpSocket::TcpNoDelay", BooleanValue (m_noDelay));
+
+  // Connect the socket writer
+  Simulator::Schedule (Seconds (1), &SocketWriter::Connect, socketWriter);
+
+  // Write 5 packets to get some bytes in flight and some acks going
+  Simulator::Schedule (Seconds (2), &SocketWriter::Write, socketWriter, 2680);
+  m_inputs.Add (536);
+  m_inputs.Add (536);
+  m_inputs.Add (536);
+  m_inputs.Add (536);
+  m_inputs.Add (536);
+
+  // Write one byte after 10 ms to ensure that some data is outstanding 
+  // and the window is big enough
+  Simulator::Schedule (Seconds (2.010), &SocketWriter::Write, socketWriter, 1);
+
+  // If Nagle is not enabled, i.e. no delay is on, add an input for a 1-byte 
+  // packet to be received
+  if (m_noDelay)
+    {
+      m_inputs.Add (1);
+    }
+
+  // One ms later, write 535 bytes, i.e. one segment size - 1
+  Simulator::Schedule (Seconds (2.012), &SocketWriter::Write, socketWriter, 535);
+
+  // If Nagle is not enabled, add an input for a 535 byte packet,
+  // otherwise, we should get a single "full" packet of 536 bytes
+  if (m_noDelay)
+    {
+      m_inputs.Add (535);
+    }
+  else
+    {
+      m_inputs.Add (536);
+    }
+
+  // Close down the socket
+  Simulator::Schedule (writerStopTimeObj, &SocketWriter::Close, socketWriter);
+
+  if (m_writeResults)
+    {
+      std::ostringstream oss;
+      if (m_noDelay)
+        {
+          oss << "tcp-no-delay-on-test-case";
+          pointToPoint.EnablePcapAll (oss.str ());
+        }
+      else
+        {
+          oss << "tcp-no-delay-off-test-case";
+          pointToPoint.EnablePcapAll (oss.str ());
+        }
+    }
+
+  Simulator::Stop (simStopTimeObj);
+  Simulator::Run ();
+  Simulator::Destroy ();
+
+  // Compare inputs and outputs
+  NS_TEST_ASSERT_MSG_EQ (m_inputs.GetN (), m_responses.GetN (), "Incorrect number of expected receive events");
+  for (uint32_t i = 0; i < m_responses.GetN (); i++)
+    {
+      uint32_t in = m_inputs.Get (i);
+      uint32_t out = m_responses.Get (i);
+      NS_TEST_ASSERT_MSG_EQ (in, out, "Mismatch:  expected " << in << " bytes, got " << out << " bytes");
+    }
+}
+
+class Ns3TcpNoDelayTestSuite : public TestSuite
+{
+public:
+  Ns3TcpNoDelayTestSuite ();
+};
+
+Ns3TcpNoDelayTestSuite::Ns3TcpNoDelayTestSuite ()
+  : TestSuite ("ns3-tcp-no-delay", SYSTEM)
+{
+  AddTestCase (new Ns3TcpNoDelayTestCase (true));
+  AddTestCase (new Ns3TcpNoDelayTestCase (false));
+}
+
+static Ns3TcpNoDelayTestSuite ns3TcpNoDelayTestSuite;
--- a/src/test/ns3tcp/wscript	Tue Dec 06 08:42:44 2011 -0500
+++ b/src/test/ns3tcp/wscript	Wed Dec 07 11:22:10 2011 -0500
@@ -28,6 +28,7 @@
         'ns3tcp-socket-test-suite.cc',
         'ns3tcp-loss-test-suite.cc',
         'ns3tcp-state-test-suite.cc',
+        'ns3tcp-no-delay-test-suite.cc',
         ]
 
     if bld.env['NSC_ENABLED']: