tcp: Add system test suite for TCP Cubic

This commit is contained in:
Tom Henderson
2024-01-08 07:37:55 -08:00
parent a72b26a3fa
commit 97279afc3e
2 changed files with 468 additions and 0 deletions

View File

@@ -22,6 +22,7 @@ if(applications
)
# cmake-format: off
set(applications_sources
ns3tcp/ns3tcp-cubic-test-suite.cc
ns3tcp/ns3tcp-loss-test-suite.cc
ns3tcp/ns3tcp-no-delay-test-suite.cc
ns3tcp/ns3tcp-socket-test-suite.cc

View File

@@ -0,0 +1,467 @@
/*
* Copyright (c) 2010 University of Washington (trace writing)
* Copyright (c) 2018-20 NITK Surathkal (topology setup)
* Copyright (c) 2024 Tom Henderson (test definition)
*
* 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
*/
// Test suite based on tcp-bbr-example.cc modified topology setup:
//
// 1 Gbps 50/100Mbps 1 Gbps
// Sender -------------- R1 -------------- R2 -------------- Receiver
// 5us baseRtt/2 5us
//
// This is a test suite written initially to test the addition of
// TCP friendliness but can also be extended for other Cubic tests. By
// changing some compile-time constants below, it can be used to also produce
// gnuplots and pcaps that demonstrate the behavior. See the WRITE_PCAP and
// WRITE_GNUPLOT booleans below to enable this output.
//
// There are four cases configured presently.
// 1. A Cubic TCP on a 5 ms RTT bottleneck, with no fast convergence nor
// TCP friendliness configured
// 2. A Cubic TCP on a 5 ms RTT bottleneck, with fast convergence but no
// TCP friendliness configured. This exhibits a different cwnd plot from 1.
// 3. A Cubic TCP on a 20 ms RTT bottleneck, with fast convergence but no
// TCP friendliness configured. There is a step change increase in
// bottleneck link capacity but the TCP is slow to exploit it.
// 4. A Cubic TCP on a 20 ms RTT bottleneck, with fast convergence and
// TCP friendliness configured. There is a step change increase in
// bottleneck link capacity and the TCP responds more quickly than in 3.
#include "ns3/abort.h"
#include "ns3/bulk-send-helper.h"
#include "ns3/config.h"
#include "ns3/data-rate.h"
#include "ns3/error-model.h"
#include "ns3/gnuplot.h"
#include "ns3/inet-socket-address.h"
#include "ns3/internet-stack-helper.h"
#include "ns3/ipv4-address-helper.h"
#include "ns3/ipv4-global-routing-helper.h"
#include "ns3/log.h"
#include "ns3/node-container.h"
#include "ns3/output-stream-wrapper.h"
#include "ns3/packet-sink-helper.h"
#include "ns3/pcap-file.h"
#include "ns3/point-to-point-helper.h"
#include "ns3/point-to-point-net-device.h"
#include "ns3/pointer.h"
#include "ns3/simulator.h"
#include "ns3/string.h"
#include "ns3/tcp-header.h"
#include "ns3/tcp-socket-factory.h"
#include "ns3/test.h"
#include "ns3/trace-helper.h"
#include "ns3/traffic-control-helper.h"
#include "ns3/uinteger.h"
#include <iomanip>
using namespace ns3;
NS_LOG_COMPONENT_DEFINE("Ns3TcpCubicTest");
static constexpr bool WRITE_PCAP = false; //!< Set to true to write out pcap.
static constexpr bool WRITE_GNUPLOT = false; //!< Set to true to write out gnuplot.
/**
* \ingroup system-tests-tcp
*/
/**
* Add sample trace values to data structures
* \param gnuplotTimeSeries Gnuplot data structure
* \param timeSeries time series of cwnd changes
* \param oldval old value of cwnd
* \param newval new value of cwnd
*/
void
CubicCwndTracer(Gnuplot2dDataset* gnuplotTimeSeries,
std::map<Time, double>* timeSeries,
uint32_t oldval,
uint32_t newval)
{
// We store data in two structures because Gnuplot2DDataset data is not exportable
NS_LOG_DEBUG("cwnd " << newval / 1448.0);
gnuplotTimeSeries->Add(Simulator::Now().GetSeconds(), newval / 1448.0);
timeSeries->insert_or_assign(Simulator::Now(), newval / 1448.0);
}
/**
* Test Cubic response
*/
class Ns3TcpCubicTestCase : public TestCase
{
public:
/**
* Constructor.
*
* \param testCase testcase name
* \param prefix filename prefix if writing output files
* \param fastConvergence whether to enable fast convergence
* \param tcpFriendliness whether to enable TCP friendliness
* \param baseRtt base RTT to use for test case
* \param capacityIncrease whether to trigger a sudden capacity increase
*/
Ns3TcpCubicTestCase(std::string testCase,
std::string prefix,
bool fastConvergence,
bool tcpFriendliness,
Time baseRtt,
bool capacityIncrease);
~Ns3TcpCubicTestCase() override;
private:
void DoRun() override;
/**
* Connect TCP cwnd trace after socket is instantiated
*
* \param nodeId node ID to connect to
* \param socketId socket ID to connect to
*/
void ConnectCwndTrace(uint32_t nodeId, uint32_t socketId);
/**
* Increases the device bandwidth to 100 Mbps
* \param device device to modify
*/
void IncreaseBandwidth(Ptr<PointToPointNetDevice> device);
/**
* Check that time series values within a time range are within a value range.
* \param start start of time range
* \param end end of time range
* \param lowerBound lower bound of acceptable values
* \param upperBound upper bound of acceptable values
* \return true if values are within range
*/
bool CheckValues(Time start, Time end, double lowerBound, double upperBound);
bool m_writeResults{WRITE_PCAP}; //!< Whether to write pcaps
bool m_writeGnuplot{WRITE_GNUPLOT}; //!< Whether to write gnuplot files
Gnuplot2dDataset m_cwndTimeSeries; //!< cwnd time series
std::map<Time, double> m_timeSeries; //!< time series to check
std::string m_prefix; //!< filename prefix if writing files
bool m_fastConvergence; //!< whether to enable fast convergence
bool m_tcpFriendliness; //!< whether to enable TCP friendliness
Time m_baseRtt; //!< the base RTT to use
bool m_capacityIncrease; //!< whether to trigger a capacity increase
};
Ns3TcpCubicTestCase::Ns3TcpCubicTestCase(std::string testCase,
std::string prefix,
bool fastConvergence,
bool tcpFriendliness,
Time baseRtt,
bool capacityIncrease)
: TestCase(testCase),
m_prefix(prefix),
m_fastConvergence(fastConvergence),
m_tcpFriendliness(tcpFriendliness),
m_baseRtt(baseRtt),
m_capacityIncrease(capacityIncrease)
{
}
Ns3TcpCubicTestCase::~Ns3TcpCubicTestCase()
{
}
void
Ns3TcpCubicTestCase::IncreaseBandwidth(Ptr<PointToPointNetDevice> device)
{
device->SetDataRate(DataRate("100Mbps"));
}
void
Ns3TcpCubicTestCase::ConnectCwndTrace(uint32_t nodeId, uint32_t socketId)
{
Config::ConnectWithoutContext(
"/NodeList/" + std::to_string(nodeId) + "/$ns3::TcpL4Protocol/SocketList/" +
std::to_string(socketId) + "/CongestionWindow",
MakeBoundCallback(&CubicCwndTracer, &m_cwndTimeSeries, &m_timeSeries));
}
bool
Ns3TcpCubicTestCase::CheckValues(Time start, Time end, double lowerBound, double upperBound)
{
auto itStart = m_timeSeries.lower_bound(start);
auto itEnd = m_timeSeries.upper_bound(end);
for (auto it = itStart; it != itEnd; it++)
{
if (it->second < lowerBound || it->second > upperBound)
{
NS_LOG_DEBUG("Value " << it->second << " at time " << it->first.GetSeconds()
<< " outside range (" << lowerBound << "," << upperBound << ")");
return false;
}
}
return true;
}
void
Ns3TcpCubicTestCase::DoRun()
{
NS_LOG_DEBUG("Running " << m_prefix);
// The maximum bandwidth delay product is 20 ms (base RTT) times 100 Mbps
// which works out to 250KB. Setting the socket buffer sizes to four times
// that value (1MB) should be more than enough.
Config::SetDefault("ns3::TcpSocket::SndBufSize", UintegerValue(1000000));
Config::SetDefault("ns3::TcpSocket::RcvBufSize", UintegerValue(1000000));
Config::SetDefault("ns3::TcpSocket::InitialCwnd", UintegerValue(10));
Config::SetDefault("ns3::TcpSocket::DelAckCount", UintegerValue(2));
Config::SetDefault("ns3::TcpSocket::SegmentSize", UintegerValue(1448));
Config::SetDefault("ns3::TcpSocketState::EnablePacing", BooleanValue(true));
Config::SetDefault("ns3::TcpSocketState::PaceInitialWindow", BooleanValue(true));
Config::SetDefault("ns3::TcpCubic::FastConvergence", BooleanValue(m_fastConvergence));
Config::SetDefault("ns3::TcpCubic::TcpFriendliness", BooleanValue(m_tcpFriendliness));
Time stopTime = Seconds(20);
NodeContainer sender;
NodeContainer receiver;
NodeContainer routers;
sender.Create(1);
receiver.Create(1);
routers.Create(2);
// Create the point-to-point link helpers
PointToPointHelper bottleneckLink;
// With the DynamicQueueLimits setting below, the following statement
// will minimize the buffering in the device and force most buffering
// into the AQM
bottleneckLink.SetQueue("ns3::DropTailQueue", "MaxSize", StringValue("2p"));
if (m_capacityIncrease)
{
// Start at a lower capacity, and increase later
bottleneckLink.SetDeviceAttribute("DataRate", StringValue("50Mbps"));
}
else
{
bottleneckLink.SetDeviceAttribute("DataRate", StringValue("100Mbps"));
}
bottleneckLink.SetChannelAttribute("Delay", TimeValue(m_baseRtt / 2));
PointToPointHelper edgeLink;
edgeLink.SetDeviceAttribute("DataRate", StringValue("1000Mbps"));
edgeLink.SetChannelAttribute("Delay", StringValue("5us"));
// Create NetDevice containers
NetDeviceContainer senderEdge = edgeLink.Install(sender.Get(0), routers.Get(0));
NetDeviceContainer r1r2 = bottleneckLink.Install(routers.Get(0), routers.Get(1));
NetDeviceContainer receiverEdge = edgeLink.Install(routers.Get(1), receiver.Get(0));
// Install Stack
InternetStackHelper internet;
internet.Install(sender);
internet.Install(receiver);
internet.Install(routers);
// Configure the root queue discipline
TrafficControlHelper tch;
tch.SetRootQueueDisc("ns3::FqCoDelQueueDisc");
tch.SetQueueLimits("ns3::DynamicQueueLimits");
tch.Install(senderEdge);
tch.Install(receiverEdge);
// Assign IP addresses
Ipv4AddressHelper ipv4;
ipv4.SetBase("10.0.0.0", "255.255.255.0");
Ipv4InterfaceContainer i1i2 = ipv4.Assign(r1r2);
ipv4.NewNetwork();
Ipv4InterfaceContainer is1 = ipv4.Assign(senderEdge);
ipv4.NewNetwork();
Ipv4InterfaceContainer ir1 = ipv4.Assign(receiverEdge);
// Populate routing tables
Ipv4GlobalRoutingHelper::PopulateRoutingTables();
// Select sender side port
uint16_t port = 50001;
// Install application on the sender
BulkSendHelper source("ns3::TcpSocketFactory", InetSocketAddress(ir1.GetAddress(1), port));
source.SetAttribute("MaxBytes", UintegerValue(0));
ApplicationContainer sourceApps = source.Install(sender.Get(0));
sourceApps.Start(Seconds(0.1));
// Hook trace source after application starts
Simulator::Schedule(Seconds(0.1) + MilliSeconds(1),
&Ns3TcpCubicTestCase::ConnectCwndTrace,
this,
0,
0);
sourceApps.Stop(stopTime);
// Install application on the receiver
PacketSinkHelper sink("ns3::TcpSocketFactory", InetSocketAddress(Ipv4Address::GetAny(), port));
ApplicationContainer sinkApps = sink.Install(receiver.Get(0));
sinkApps.Start(Seconds(0.0));
sinkApps.Stop(stopTime);
if (m_capacityIncrease)
{
// Double the capacity of the bottleneck link at 10 seconds
Ptr<PointToPointNetDevice> device = r1r2.Get(0)->GetObject<PointToPointNetDevice>();
Simulator::Schedule(Seconds(10), &Ns3TcpCubicTestCase::IncreaseBandwidth, this, device);
}
std::ostringstream oss;
oss << m_prefix;
if (m_writeResults)
{
edgeLink.EnablePcap(oss.str(), senderEdge.Get(0));
edgeLink.EnablePcap(oss.str(), receiverEdge.Get(0));
edgeLink.EnableAscii(oss.str(), senderEdge.Get(0));
edgeLink.EnableAscii(oss.str(), receiverEdge.Get(0));
}
Simulator::Stop(Seconds(30));
Simulator::Run();
if (m_writeGnuplot)
{
std::ofstream outfile(oss.str() + ".plt");
std::string capacityIncreaseString;
if (m_capacityIncrease)
{
capacityIncreaseString = " capacity increase = 1";
}
Gnuplot gnuplot = Gnuplot(
oss.str() + ".eps",
"Cubic concave/convex response (" + std::to_string(m_baseRtt.GetMilliSeconds()) +
" ms RTT, 1448 byte segs, 100 Mbps bottleneck)\\nFast convergence = " +
std::to_string(m_fastConvergence) +
" friendliness = " + std::to_string(m_tcpFriendliness) + capacityIncreaseString);
gnuplot.SetTerminal("post eps color enhanced");
gnuplot.SetLegend("Time (seconds)", "Cwnd (segments)");
m_cwndTimeSeries.SetTitle("");
gnuplot.AddDataset(m_cwndTimeSeries);
gnuplot.GenerateOutput(outfile);
}
// Check that cwnd values are within tolerance of expected values
// The expected values were determined by inspecting the plots generated
if (m_prefix == "ns3-tcp-cubic-no-heuristic")
{
// Check overall min and max
NS_TEST_ASSERT_MSG_EQ(CheckValues(Seconds(1), Seconds(19), 50, 90),
true,
"cwnd outside range");
// Time just before a reduction does not have much variation
NS_TEST_ASSERT_MSG_EQ(CheckValues(Seconds(9), Seconds(9.7), 84, 89),
true,
"cwnd outside range");
}
else if (m_prefix == "ns3-tcp-cubic-fast-conv")
{
// Check overall min and max
NS_TEST_ASSERT_MSG_EQ(CheckValues(Seconds(1), Seconds(19), 50, 90),
true,
"cwnd outside range");
// Initial convex region does not have much variation
NS_TEST_ASSERT_MSG_EQ(CheckValues(Seconds(4), Seconds(6), 83, 88),
true,
"cwnd outside range");
}
else if (m_prefix == "ns3-tcp-cubic-no-friendly")
{
// Between time 12 and 16, cwnd should be fairly constant
// because without TCP friendliness, Cubic does not respond quickly
NS_TEST_ASSERT_MSG_EQ(CheckValues(Seconds(12), Seconds(16), 107, 123),
true,
"cwnd outside range");
// After time 19.5, cwnd should have grown much higher
NS_TEST_ASSERT_MSG_EQ(CheckValues(Seconds(19.5), Seconds(20), 180, 210),
true,
"cwnd outside range");
}
else if (m_prefix == "ns3-tcp-cubic-friendly")
{
// In contrast to previous case, cwnd should grow above 150 much sooner
NS_TEST_ASSERT_MSG_EQ(CheckValues(Seconds(12), Seconds(14), 150, 210),
true,
"cwnd outside range");
}
Simulator::Destroy();
}
/**
* \ingroup tcp-cubic-tests
* TestSuite for module tcp-cubic
*/
class Ns3TcpCubicTestSuite : public TestSuite
{
public:
Ns3TcpCubicTestSuite();
};
Ns3TcpCubicTestSuite::Ns3TcpCubicTestSuite()
: TestSuite("ns3-tcp-cubic", UNIT)
{
// Test Cubic with no fast convergence or TCP friendliness enabled
// This results in a cwnd plot that has only the concave portion of
// returning cwnd to Wmax
AddTestCase(new Ns3TcpCubicTestCase("no heuristic",
"ns3-tcp-cubic-no-heuristic",
false,
false,
MilliSeconds(5),
false),
TestCase::QUICK);
// Test Cubic with fast convergence but no TCP friendliness enabled
// This results in a cwnd plot that has concave and convex regions, as
// well as convex-only regions; also observed on Linux testbeds
AddTestCase(new Ns3TcpCubicTestCase("fast convergence on",
"ns3-tcp-cubic-fast-conv",
true,
false,
MilliSeconds(5),
false),
TestCase::QUICK);
// Test Cubic with fast convergence but no TCP friendliness enabled
// with a higher RTT (20ms) and a step change in capacity at time 10s.
// This results in a sluggish response by TCP Cubic to use the new capacity.
AddTestCase(new Ns3TcpCubicTestCase("fast convergence on, Reno friendliness off",
"ns3-tcp-cubic-no-friendly",
true,
false,
MilliSeconds(20),
true),
TestCase::QUICK);
// Test Cubic with fast convergence but with TCP friendliness enabled
// with a higher RTT (20ms) and a step change in capacity at time 10s.
// This results in a faster response by TCP Cubic to use the new capacity.
AddTestCase(new Ns3TcpCubicTestCase("fast convergence on, Reno friendliness on",
"ns3-tcp-cubic-friendly",
true,
true,
MilliSeconds(20),
true),
TestCase::QUICK);
}
/**
* \ingroup tcp-cubic-tests
* Static variable for test initialization
*/
static Ns3TcpCubicTestSuite ns3TcpCubicTestSuite;