A Discrete-Event Network Simulator
API
ns3tcp-cubic-test-suite.cc
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2010 University of Washington (trace writing)
3  * Copyright (c) 2018-20 NITK Surathkal (topology setup)
4  * Copyright (c) 2024 Tom Henderson (test definition)
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License version 2 as
8  * published by the Free Software Foundation;
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18  */
19 
20 // Test suite based on tcp-bbr-example.cc modified topology setup:
21 //
22 // 1 Gbps 50/100Mbps 1 Gbps
23 // Sender -------------- R1 -------------- R2 -------------- Receiver
24 // 5us baseRtt/2 5us
25 //
26 // This is a test suite written initially to test the addition of
27 // TCP friendliness but can also be extended for other Cubic tests. By
28 // changing some compile-time constants below, it can be used to also produce
29 // gnuplots and pcaps that demonstrate the behavior. See the WRITE_PCAP and
30 // WRITE_GNUPLOT booleans below to enable this output.
31 //
32 // There are four cases configured presently.
33 // 1. A Cubic TCP on a 5 ms RTT bottleneck, with no fast convergence nor
34 // TCP friendliness configured
35 // 2. A Cubic TCP on a 5 ms RTT bottleneck, with fast convergence but no
36 // TCP friendliness configured. This exhibits a different cwnd plot from 1.
37 // 3. A Cubic TCP on a 20 ms RTT bottleneck, with fast convergence but no
38 // TCP friendliness configured. There is a step change increase in
39 // bottleneck link capacity but the TCP is slow to exploit it.
40 // 4. A Cubic TCP on a 20 ms RTT bottleneck, with fast convergence and
41 // TCP friendliness configured. There is a step change increase in
42 // bottleneck link capacity and the TCP responds more quickly than in 3.
43 
44 #include "ns3/abort.h"
45 #include "ns3/bulk-send-helper.h"
46 #include "ns3/config.h"
47 #include "ns3/data-rate.h"
48 #include "ns3/error-model.h"
49 #include "ns3/gnuplot.h"
50 #include "ns3/inet-socket-address.h"
51 #include "ns3/internet-stack-helper.h"
52 #include "ns3/ipv4-address-helper.h"
53 #include "ns3/ipv4-global-routing-helper.h"
54 #include "ns3/log.h"
55 #include "ns3/node-container.h"
56 #include "ns3/output-stream-wrapper.h"
57 #include "ns3/packet-sink-helper.h"
58 #include "ns3/pcap-file.h"
59 #include "ns3/point-to-point-helper.h"
60 #include "ns3/point-to-point-net-device.h"
61 #include "ns3/pointer.h"
62 #include "ns3/simulator.h"
63 #include "ns3/string.h"
64 #include "ns3/tcp-header.h"
65 #include "ns3/tcp-socket-factory.h"
66 #include "ns3/test.h"
67 #include "ns3/trace-helper.h"
68 #include "ns3/traffic-control-helper.h"
69 #include "ns3/uinteger.h"
70 
71 #include <iomanip>
72 
73 using namespace ns3;
74 
75 NS_LOG_COMPONENT_DEFINE("Ns3TcpCubicTest");
76 
77 static constexpr bool WRITE_PCAP = false;
78 static constexpr bool WRITE_GNUPLOT = false;
79 
91 void
92 CubicCwndTracer(Gnuplot2dDataset* gnuplotTimeSeries,
93  std::map<Time, double>* timeSeries,
94  uint32_t oldval,
95  uint32_t newval)
96 {
97  // We store data in two structures because Gnuplot2DDataset data is not exportable
98  NS_LOG_DEBUG("cwnd " << newval / 1448.0);
99  gnuplotTimeSeries->Add(Simulator::Now().GetSeconds(), newval / 1448.0);
100  timeSeries->insert_or_assign(Simulator::Now(), newval / 1448.0);
101 }
102 
107 {
108  public:
119  Ns3TcpCubicTestCase(std::string testCase,
120  std::string prefix,
121  bool fastConvergence,
122  bool tcpFriendliness,
123  Time baseRtt,
124  bool capacityIncrease);
125  ~Ns3TcpCubicTestCase() override;
126 
127  private:
128  void DoRun() override;
129 
136  void ConnectCwndTrace(uint32_t nodeId, uint32_t socketId);
137 
142  void IncreaseBandwidth(Ptr<PointToPointNetDevice> device);
143 
152  bool CheckValues(Time start, Time end, double lowerBound, double upperBound);
153 
154  bool m_writeResults{WRITE_PCAP};
155  bool m_writeGnuplot{WRITE_GNUPLOT};
157  std::map<Time, double> m_timeSeries;
158  std::string m_prefix;
163 };
164 
166  std::string prefix,
167  bool fastConvergence,
168  bool tcpFriendliness,
169  Time baseRtt,
170  bool capacityIncrease)
171  : TestCase(testCase),
172  m_prefix(prefix),
173  m_fastConvergence(fastConvergence),
174  m_tcpFriendliness(tcpFriendliness),
175  m_baseRtt(baseRtt),
176  m_capacityIncrease(capacityIncrease)
177 {
178 }
179 
181 {
182 }
183 
184 void
186 {
187  device->SetDataRate(DataRate("100Mbps"));
188 }
189 
190 void
191 Ns3TcpCubicTestCase::ConnectCwndTrace(uint32_t nodeId, uint32_t socketId)
192 {
194  "/NodeList/" + std::to_string(nodeId) + "/$ns3::TcpL4Protocol/SocketList/" +
195  std::to_string(socketId) + "/CongestionWindow",
197 }
198 
199 bool
200 Ns3TcpCubicTestCase::CheckValues(Time start, Time end, double lowerBound, double upperBound)
201 {
202  auto itStart = m_timeSeries.lower_bound(start);
203  auto itEnd = m_timeSeries.upper_bound(end);
204  for (auto it = itStart; it != itEnd; it++)
205  {
206  if (it->second < lowerBound || it->second > upperBound)
207  {
208  NS_LOG_DEBUG("Value " << it->second << " at time " << it->first.GetSeconds()
209  << " outside range (" << lowerBound << "," << upperBound << ")");
210  return false;
211  }
212  }
213  return true;
214 }
215 
216 void
218 {
219  NS_LOG_DEBUG("Running " << m_prefix);
220  // The maximum bandwidth delay product is 20 ms (base RTT) times 100 Mbps
221  // which works out to 250KB. Setting the socket buffer sizes to four times
222  // that value (1MB) should be more than enough.
223  Config::SetDefault("ns3::TcpSocket::SndBufSize", UintegerValue(1000000));
224  Config::SetDefault("ns3::TcpSocket::RcvBufSize", UintegerValue(1000000));
225  Config::SetDefault("ns3::TcpSocket::InitialCwnd", UintegerValue(10));
226  Config::SetDefault("ns3::TcpSocket::DelAckCount", UintegerValue(2));
227  Config::SetDefault("ns3::TcpSocket::SegmentSize", UintegerValue(1448));
228  Config::SetDefault("ns3::TcpSocketState::EnablePacing", BooleanValue(true));
229  Config::SetDefault("ns3::TcpSocketState::PaceInitialWindow", BooleanValue(true));
230  Config::SetDefault("ns3::TcpCubic::FastConvergence", BooleanValue(m_fastConvergence));
231  Config::SetDefault("ns3::TcpCubic::TcpFriendliness", BooleanValue(m_tcpFriendliness));
232 
233  Time stopTime = Seconds(20);
234 
235  NodeContainer sender;
236  NodeContainer receiver;
237  NodeContainer routers;
238  sender.Create(1);
239  receiver.Create(1);
240  routers.Create(2);
241 
242  // Create the point-to-point link helpers
243  PointToPointHelper bottleneckLink;
244  // With the DynamicQueueLimits setting below, the following statement
245  // will minimize the buffering in the device and force most buffering
246  // into the AQM
247  bottleneckLink.SetQueue("ns3::DropTailQueue", "MaxSize", StringValue("2p"));
248  if (m_capacityIncrease)
249  {
250  // Start at a lower capacity, and increase later
251  bottleneckLink.SetDeviceAttribute("DataRate", StringValue("50Mbps"));
252  }
253  else
254  {
255  bottleneckLink.SetDeviceAttribute("DataRate", StringValue("100Mbps"));
256  }
257  bottleneckLink.SetChannelAttribute("Delay", TimeValue(m_baseRtt / 2));
258 
259  PointToPointHelper edgeLink;
260  edgeLink.SetDeviceAttribute("DataRate", StringValue("1000Mbps"));
261  edgeLink.SetChannelAttribute("Delay", StringValue("5us"));
262 
263  // Create NetDevice containers
264  NetDeviceContainer senderEdge = edgeLink.Install(sender.Get(0), routers.Get(0));
265  NetDeviceContainer r1r2 = bottleneckLink.Install(routers.Get(0), routers.Get(1));
266  NetDeviceContainer receiverEdge = edgeLink.Install(routers.Get(1), receiver.Get(0));
267 
268  // Install Stack
270  internet.Install(sender);
271  internet.Install(receiver);
272  internet.Install(routers);
273 
274  // Configure the root queue discipline
276  tch.SetRootQueueDisc("ns3::FqCoDelQueueDisc");
277  tch.SetQueueLimits("ns3::DynamicQueueLimits");
278 
279  tch.Install(senderEdge);
280  tch.Install(receiverEdge);
281 
282  // Assign IP addresses
284  ipv4.SetBase("10.0.0.0", "255.255.255.0");
285 
286  Ipv4InterfaceContainer i1i2 = ipv4.Assign(r1r2);
287  ipv4.NewNetwork();
288  Ipv4InterfaceContainer is1 = ipv4.Assign(senderEdge);
289  ipv4.NewNetwork();
290  Ipv4InterfaceContainer ir1 = ipv4.Assign(receiverEdge);
291 
292  // Populate routing tables
293  Ipv4GlobalRoutingHelper::PopulateRoutingTables();
294 
295  // Select sender side port
296  uint16_t port = 50001;
297 
298  // Install application on the sender
299  BulkSendHelper source("ns3::TcpSocketFactory", InetSocketAddress(ir1.GetAddress(1), port));
300  source.SetAttribute("MaxBytes", UintegerValue(0));
301  ApplicationContainer sourceApps = source.Install(sender.Get(0));
302  sourceApps.Start(Seconds(0.1));
303  // Hook trace source after application starts
304  Simulator::Schedule(Seconds(0.1) + MilliSeconds(1),
306  this,
307  0,
308  0);
309  sourceApps.Stop(stopTime);
310 
311  // Install application on the receiver
312  PacketSinkHelper sink("ns3::TcpSocketFactory", InetSocketAddress(Ipv4Address::GetAny(), port));
313  ApplicationContainer sinkApps = sink.Install(receiver.Get(0));
314  sinkApps.Start(Seconds(0.0));
315  sinkApps.Stop(stopTime);
316 
317  if (m_capacityIncrease)
318  {
319  // Double the capacity of the bottleneck link at 10 seconds
320  Ptr<PointToPointNetDevice> device = r1r2.Get(0)->GetObject<PointToPointNetDevice>();
321  Simulator::Schedule(Seconds(10), &Ns3TcpCubicTestCase::IncreaseBandwidth, this, device);
322  }
323 
324  std::ostringstream oss;
325  oss << m_prefix;
326  if (m_writeResults)
327  {
328  edgeLink.EnablePcap(oss.str(), senderEdge.Get(0));
329  edgeLink.EnablePcap(oss.str(), receiverEdge.Get(0));
330  edgeLink.EnableAscii(oss.str(), senderEdge.Get(0));
331  edgeLink.EnableAscii(oss.str(), receiverEdge.Get(0));
332  }
333  Simulator::Stop(Seconds(30));
334  Simulator::Run();
335 
336  if (m_writeGnuplot)
337  {
338  std::ofstream outfile(oss.str() + ".plt");
339 
340  std::string capacityIncreaseString;
341  if (m_capacityIncrease)
342  {
343  capacityIncreaseString = " capacity increase = 1";
344  }
345  Gnuplot gnuplot = Gnuplot(
346  oss.str() + ".eps",
347  "Cubic concave/convex response (" + std::to_string(m_baseRtt.GetMilliSeconds()) +
348  " ms RTT, 1448 byte segs, 100 Mbps bottleneck)\\nFast convergence = " +
350  " friendliness = " + std::to_string(m_tcpFriendliness) + capacityIncreaseString);
351  gnuplot.SetTerminal("post eps color enhanced");
352  gnuplot.SetLegend("Time (seconds)", "Cwnd (segments)");
354  gnuplot.AddDataset(m_cwndTimeSeries);
355  gnuplot.GenerateOutput(outfile);
356  }
357 
358  // Check that cwnd values are within tolerance of expected values
359  // The expected values were determined by inspecting the plots generated
360  if (m_prefix == "ns3-tcp-cubic-no-heuristic")
361  {
362  // Check overall min and max
364  true,
365  "cwnd outside range");
366  // Time just before a reduction does not have much variation
368  true,
369  "cwnd outside range");
370  }
371  else if (m_prefix == "ns3-tcp-cubic-fast-conv")
372  {
373  // Check overall min and max
375  true,
376  "cwnd outside range");
377  // Initial convex region does not have much variation
379  true,
380  "cwnd outside range");
381  }
382  else if (m_prefix == "ns3-tcp-cubic-no-friendly")
383  {
384  // Between time 12 and 16, cwnd should be fairly constant
385  // because without TCP friendliness, Cubic does not respond quickly
386  NS_TEST_ASSERT_MSG_EQ(CheckValues(Seconds(12), Seconds(16), 107, 123),
387  true,
388  "cwnd outside range");
389  // After time 19.5, cwnd should have grown much higher
390  NS_TEST_ASSERT_MSG_EQ(CheckValues(Seconds(19.5), Seconds(20), 180, 210),
391  true,
392  "cwnd outside range");
393  }
394  else if (m_prefix == "ns3-tcp-cubic-friendly")
395  {
396  // In contrast to previous case, cwnd should grow above 150 much sooner
397  NS_TEST_ASSERT_MSG_EQ(CheckValues(Seconds(12), Seconds(14), 150, 210),
398  true,
399  "cwnd outside range");
400  }
401 
402  Simulator::Destroy();
403 }
404 
410 {
411  public:
413 };
414 
416  : TestSuite("ns3-tcp-cubic", UNIT)
417 {
418  // Test Cubic with no fast convergence or TCP friendliness enabled
419  // This results in a cwnd plot that has only the concave portion of
420  // returning cwnd to Wmax
421  AddTestCase(new Ns3TcpCubicTestCase("no heuristic",
422  "ns3-tcp-cubic-no-heuristic",
423  false,
424  false,
425  MilliSeconds(5),
426  false),
427  TestCase::QUICK);
428 
429  // Test Cubic with fast convergence but no TCP friendliness enabled
430  // This results in a cwnd plot that has concave and convex regions, as
431  // well as convex-only regions; also observed on Linux testbeds
432  AddTestCase(new Ns3TcpCubicTestCase("fast convergence on",
433  "ns3-tcp-cubic-fast-conv",
434  true,
435  false,
436  MilliSeconds(5),
437  false),
438  TestCase::QUICK);
439 
440  // Test Cubic with fast convergence but no TCP friendliness enabled
441  // with a higher RTT (20ms) and a step change in capacity at time 10s.
442  // This results in a sluggish response by TCP Cubic to use the new capacity.
443  AddTestCase(new Ns3TcpCubicTestCase("fast convergence on, Reno friendliness off",
444  "ns3-tcp-cubic-no-friendly",
445  true,
446  false,
447  MilliSeconds(20),
448  true),
449  TestCase::QUICK);
450 
451  // Test Cubic with fast convergence but with TCP friendliness enabled
452  // with a higher RTT (20ms) and a step change in capacity at time 10s.
453  // This results in a faster response by TCP Cubic to use the new capacity.
454  AddTestCase(new Ns3TcpCubicTestCase("fast convergence on, Reno friendliness on",
455  "ns3-tcp-cubic-friendly",
456  true,
457  true,
458  MilliSeconds(20),
459  true),
460  TestCase::QUICK);
461 }
462 
Ipv4InterfaceContainer i1i2
IPv4 interface container i1 + i2.
Gnuplot2dDataset m_cwndTimeSeries
cwnd time series
bool m_tcpFriendliness
whether to enable TCP friendliness
void DoRun() override
Implementation to actually run this TestCase.
Time m_baseRtt
the base RTT to use
void ConnectCwndTrace(uint32_t nodeId, uint32_t socketId)
Connect TCP cwnd trace after socket is instantiated.
std::string m_prefix
filename prefix if writing files
Ns3TcpCubicTestCase(std::string testCase, std::string prefix, bool fastConvergence, bool tcpFriendliness, Time baseRtt, bool capacityIncrease)
Constructor.
bool m_writeGnuplot
Whether to write gnuplot files.
bool m_capacityIncrease
whether to trigger a capacity increase
bool m_fastConvergence
whether to enable fast convergence
bool m_writeResults
Whether to write pcaps.
std::map< Time, double > m_timeSeries
time series to check
void IncreaseBandwidth(Ptr< PointToPointNetDevice > device)
Increases the device bandwidth to 100 Mbps.
bool CheckValues(Time start, Time end, double lowerBound, double upperBound)
Check that time series values within a time range are within a value range.
TestSuite for module tcp-cubic.
holds a vector of ns3::Application pointers.
void Start(Time start) const
Start all of the Applications in this container at the start time given as a parameter.
void Stop(Time stop) const
Arrange for all of the Applications in this container to Stop() at the Time given as a parameter.
void EnableAscii(std::string prefix, Ptr< NetDevice > nd, bool explicitFilename=false)
Enable ascii trace output on the indicated net device.
A helper to make it easier to instantiate an ns3::BulkSendApplication on a set of nodes.
void SetAttribute(std::string name, const AttributeValue &value)
Helper function used to set the underlying application attributes, not the socket attributes.
ApplicationContainer Install(NodeContainer c) const
Install an ns3::BulkSendApplication on each node of the input container configured with all the attri...
Class to represent a 2D points plot.
Definition: gnuplot.h:118
void Add(double x, double y)
Definition: gnuplot.cc:362
void SetTitle(const std::string &title)
Change line title.
Definition: gnuplot.cc:141
a simple class to generate gnuplot-ready plotting commands from a set of datasets.
Definition: gnuplot.h:373
void AddDataset(const GnuplotDataset &dataset)
Definition: gnuplot.cc:759
void SetLegend(const std::string &xLegend, const std::string &yLegend)
Definition: gnuplot.cc:739
void SetTerminal(const std::string &terminal)
Definition: gnuplot.cc:727
void GenerateOutput(std::ostream &os)
Writes gnuplot commands and data values to a single output stream.
Definition: gnuplot.cc:765
an Inet address class
aggregate IP/TCP/UDP functionality to existing Nodes.
A helper class to make life easier while doing simple IPv4 address assignment in scripts.
holds a vector of std::pair of Ptr<Ipv4> and interface index.
Ipv4Address GetAddress(uint32_t i, uint32_t j=0) const
holds a vector of ns3::NetDevice pointers
Ptr< NetDevice > Get(uint32_t i) const
Get the Ptr<NetDevice> stored in this container at a given index.
keep track of a set of node pointers.
void Create(uint32_t n)
Create n nodes and append pointers to them to the end of this NodeContainer.
Ptr< Node > Get(uint32_t i) const
Get the Ptr<Node> stored in this container at a given index.
A helper to make it easier to instantiate an ns3::PacketSinkApplication on a set of nodes.
void EnablePcap(std::string prefix, Ptr< NetDevice > nd, bool promiscuous=false, bool explicitFilename=false)
Enable pcap output the indicated net device.
Build a set of PointToPointNetDevice objects.
void SetDeviceAttribute(std::string name, const AttributeValue &value)
Set an attribute value to be propagated to each NetDevice created by the helper.
void SetChannelAttribute(std::string name, const AttributeValue &value)
Set an attribute value to be propagated to each Channel created by the helper.
void SetQueue(std::string type, Ts &&... args)
Each point to point net device must have a queue to pass packets through.
NetDeviceContainer Install(NodeContainer c)
A Device for a Point to Point Network Link.
Smart pointer class similar to boost::intrusive_ptr.
Definition: ptr.h:77
static Time Now()
Return the current simulation virtual time.
Definition: simulator.cc:208
Hold variables of type string.
Definition: string.h:56
encapsulates test code
Definition: test.h:1060
void AddTestCase(TestCase *testCase, TestDuration duration=QUICK)
Add an individual child TestCase to this test suite.
Definition: test.cc:301
A suite of tests to run.
Definition: test.h:1256
Simulation virtual time values and global simulation resolution.
Definition: nstime.h:105
int64_t GetMilliSeconds() const
Get an approximation of the time stored in this instance in the indicated unit.
Definition: nstime.h:408
Build a set of QueueDisc objects.
QueueDiscContainer Install(NetDeviceContainer c)
uint16_t SetRootQueueDisc(const std::string &type, Args &&... args)
Helper function used to set a root queue disc of the given type and with the given attributes.
void SetQueueLimits(std::string type, Args &&... args)
Helper function used to add a queue limits object to the transmission queues of the devices.
Hold an unsigned integer type.
Definition: uinteger.h:45
uint16_t port
Definition: dsdv-manet.cc:44
Time stopTime
void SetDefault(std::string name, const AttributeValue &value)
Definition: config.cc:890
void ConnectWithoutContext(std::string path, const CallbackBase &cb)
Definition: config.cc:950
#define NS_LOG_COMPONENT_DEFINE(name)
Define a Log component with a specific name.
Definition: log.h:202
#define NS_LOG_DEBUG(msg)
Use NS_LOG to output a message of level LOG_DEBUG.
Definition: log.h:268
auto MakeBoundCallback(R(*fnPtr)(Args...), BArgs &&... bargs)
Make Callbacks with varying number of bound arguments.
Definition: callback.h:765
void(* DataRate)(DataRate oldValue, DataRate newValue)
TracedValue callback signature for DataRate.
Definition: data-rate.h:327
void CubicCwndTracer(Gnuplot2dDataset *gnuplotTimeSeries, std::map< Time, double > *timeSeries, uint32_t oldval, uint32_t newval)
Add sample trace values to data structures.
#define NS_TEST_ASSERT_MSG_EQ(actual, limit, msg)
Test that an actual and expected (limit) value are equal and report and abort if not.
Definition: test.h:144
Time Seconds(double value)
Construct a Time in the indicated unit.
Definition: nstime.h:1326
Time MilliSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition: nstime.h:1338
NLOHMANN_BASIC_JSON_TPL_DECLARATION std::string to_string(const NLOHMANN_BASIC_JSON_TPL &j)
user-defined to_string function for JSON values
Definition: json.h:25255
Every class exported by the ns3 library is enclosed in the ns3 namespace.
static constexpr bool WRITE_PCAP
Set to true to write out pcap.
static Ns3TcpCubicTestSuite ns3TcpCubicTestSuite
Static variable for test initialization.
static constexpr bool WRITE_GNUPLOT
Set to true to write out gnuplot.
Ptr< PacketSink > sink
Pointer to the packet sink application.
Definition: wifi-tcp.cc:55