TCP Reno, NewReno, Cubic, and Vegas Comparison under 5% Random Packet Loss in Wired Topology | NS3 Project 23
TCP Reno, NewReno, Cubic, and Vegas Comparison under 5% Random Packet Loss in Wired Topology
Prompt Used
LLM Used: Claude (Anthropic)
Prompt:
Write NS-3 C++ code to compare TCP Reno, NewReno, Cubic, and Vegas under 5% random packet loss in a wired topology. The simulation should:
- Create a point-to-point wired network topology
- Implement 5% random packet loss using error model
- Run simulations for all four TCP variants
- Generate NetAnim XML file for visualization
- Output throughput data for gnuplot graphs
- Use proper NS-3 coding conventions
The file should be named tcp_comparison.cc and run with ./ns3 run scratch/tcp_comparison.cc
Source Code (tcp_comparison.cc)
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* TCP Variants Comparison under 5% Packet Loss
* Comparing: Reno, NewReno, Cubic, Vegas
* Wired Topology with Random Packet Loss
*/
#include "ns3/core-module.h"
#include "ns3/network-module.h"
#include "ns3/internet-module.h"
#include "ns3/point-to-point-module.h"
#include "ns3/applications-module.h"
#include "ns3/flow-monitor-module.h"
#include "ns3/netanim-module.h"
#include "ns3/traffic-control-module.h"
#include <fstream>
#include <string>
#include <map>
using namespace ns3;
NS_LOG_COMPONENT_DEFINE("TcpVariantsComparison");
// Global variables for tracing
std::map<std::string, std::ofstream*> cwndFiles;
std::map<std::string, std::ofstream*> throughputFiles;
std::map<std::string, uint64_t> lastTotalRx;
std::map<std::string, Ptr<PacketSink>> sinkApps;
// Congestion window tracer
void CwndTracer(std::string context, uint32_t oldCwnd, uint32_t newCwnd)
{
std::string variant = context;
if (cwndFiles.find(variant) != cwndFiles.end() && cwndFiles[variant]->is_open())
{
*cwndFiles[variant] << Simulator::Now().GetSeconds() << " " << newCwnd << std::endl;
}
}
// Throughput calculator
void CalculateThroughput(std::string variant)
{
if (sinkApps.find(variant) != sinkApps.end())
{
uint64_t totalRx = sinkApps[variant]->GetTotalRx();
double throughput = (totalRx - lastTotalRx[variant]) * 8.0 / 0.1 / 1000000; // Mbps
lastTotalRx[variant] = totalRx;
if (throughputFiles.find(variant) != throughputFiles.end() && throughputFiles[variant]->is_open())
{
*throughputFiles[variant] << Simulator::Now().GetSeconds() << " " << throughput << std::endl;
}
}
if (Simulator::Now().GetSeconds() < 30.0)
{
Simulator::Schedule(Seconds(0.1), &CalculateThroughput, variant);
}
}
// Run simulation for a specific TCP variant
void RunSimulation(std::string tcpVariant, double packetLossRate)
{
std::cout << "\n========================================" << std::endl;
std::cout << "Running simulation for TCP " << tcpVariant << std::endl;
std::cout << "Packet Loss Rate: " << packetLossRate * 100 << "%" << std::endl;
std::cout << "========================================\n" << std::endl;
// Set TCP variant
std::string tcpTypeId;
if (tcpVariant == "Reno")
{
tcpTypeId = "ns3::TcpLinuxReno";
}
else if (tcpVariant == "NewReno")
{
tcpTypeId = "ns3::TcpNewReno";
}
else if (tcpVariant == "Cubic")
{
tcpTypeId = "ns3::TcpCubic";
}
else if (tcpVariant == "Vegas")
{
tcpTypeId = "ns3::TcpVegas";
}
else
{
NS_LOG_ERROR("Unknown TCP variant: " << tcpVariant);
return;
}
Config::SetDefault("ns3::TcpL4Protocol::SocketType", TypeIdValue(TypeId::LookupByName(tcpTypeId)));
Config::SetDefault("ns3::TcpSocket::SegmentSize", UintegerValue(1448));
Config::SetDefault("ns3::TcpSocket::InitialCwnd", UintegerValue(1));
// Create nodes
NodeContainer senderNode, receiverNode, routerNodes;
senderNode.Create(1);
receiverNode.Create(1);
routerNodes.Create(2);
// Create point-to-point links
PointToPointHelper p2pSender, p2pReceiver, p2pBottleneck;
// Sender to Router1: 100 Mbps, 2ms delay
p2pSender.SetDeviceAttribute("DataRate", StringValue("100Mbps"));
p2pSender.SetChannelAttribute("Delay", StringValue("2ms"));
// Router2 to Receiver: 100 Mbps, 2ms delay
p2pReceiver.SetDeviceAttribute("DataRate", StringValue("100Mbps"));
p2pReceiver.SetChannelAttribute("Delay", StringValue("2ms"));
// Bottleneck link: 10 Mbps, 10ms delay
p2pBottleneck.SetDeviceAttribute("DataRate", StringValue("10Mbps"));
p2pBottleneck.SetChannelAttribute("Delay", StringValue("10ms"));
// Install links
NetDeviceContainer senderDevices = p2pSender.Install(senderNode.Get(0), routerNodes.Get(0));
NetDeviceContainer bottleneckDevices = p2pBottleneck.Install(routerNodes.Get(0), routerNodes.Get(1));
NetDeviceContainer receiverDevices = p2pReceiver.Install(routerNodes.Get(1), receiverNode.Get(0));
// Add random packet loss to bottleneck link (5%)
Ptr<RateErrorModel> errorModel = CreateObject<RateErrorModel>();
errorModel->SetAttribute("ErrorRate", DoubleValue(packetLossRate));
errorModel->SetAttribute("ErrorUnit", EnumValue(RateErrorModel::ERROR_UNIT_PACKET));
bottleneckDevices.Get(1)->SetAttribute("ReceiveErrorModel", PointerValue(errorModel));
// Install internet stack
InternetStackHelper internet;
internet.Install(senderNode);
internet.Install(receiverNode);
internet.Install(routerNodes);
// Assign IP addresses
Ipv4AddressHelper ipv4;
ipv4.SetBase("10.1.1.0", "255.255.255.0");
Ipv4InterfaceContainer senderInterfaces = ipv4.Assign(senderDevices);
ipv4.SetBase("10.1.2.0", "255.255.255.0");
Ipv4InterfaceContainer bottleneckInterfaces = ipv4.Assign(bottleneckDevices);
ipv4.SetBase("10.1.3.0", "255.255.255.0");
Ipv4InterfaceContainer receiverInterfaces = ipv4.Assign(receiverDevices);
// Set up routing
Ipv4GlobalRoutingHelper::PopulateRoutingTables();
// Create applications
uint16_t port = 9;
// Packet sink on receiver
PacketSinkHelper sinkHelper("ns3::TcpSocketFactory",
InetSocketAddress(Ipv4Address::GetAny(), port));
ApplicationContainer sinkApp = sinkHelper.Install(receiverNode.Get(0));
sinkApp.Start(Seconds(0.0));
sinkApp.Stop(Seconds(30.0));
sinkApps[tcpVariant] = DynamicCast<PacketSink>(sinkApp.Get(0));
lastTotalRx[tcpVariant] = 0;
// Bulk send on sender
BulkSendHelper sourceHelper("ns3::TcpSocketFactory",
InetSocketAddress(receiverInterfaces.GetAddress(1), port));
sourceHelper.SetAttribute("MaxBytes", UintegerValue(0)); // Unlimited
sourceHelper.SetAttribute("SendSize", UintegerValue(1448));
ApplicationContainer sourceApp = sourceHelper.Install(senderNode.Get(0));
sourceApp.Start(Seconds(1.0));
sourceApp.Stop(Seconds(29.0));
// Open output files
std::string cwndFilename = "tcp-" + tcpVariant + "-cwnd.dat";
std::string throughputFilename = "tcp-" + tcpVariant + "-throughput.dat";
cwndFiles[tcpVariant] = new std::ofstream(cwndFilename.c_str());
throughputFiles[tcpVariant] = new std::ofstream(throughputFilename.c_str());
// Set up congestion window tracing
Simulator::Schedule(Seconds(1.001), [tcpVariant]() {
Config::ConnectWithoutContext("/NodeList/0/$ns3::TcpL4Protocol/SocketList/0/CongestionWindow",
MakeBoundCallback(&CwndTracer, tcpVariant));
});
// Schedule throughput calculation
Simulator::Schedule(Seconds(1.1), &CalculateThroughput, tcpVariant);
// Flow monitor
FlowMonitorHelper flowHelper;
Ptr<FlowMonitor> flowMonitor = flowHelper.InstallAll();
// NetAnim setup
std::string animFilename = "tcp-" + tcpVariant + "-animation.xml";
AnimationInterface anim(animFilename);
// Set node positions for visualization
anim.SetConstantPosition(senderNode.Get(0), 10.0, 50.0);
anim.SetConstantPosition(routerNodes.Get(0), 40.0, 50.0);
anim.SetConstantPosition(routerNodes.Get(1), 70.0, 50.0);
anim.SetConstantPosition(receiverNode.Get(0), 100.0, 50.0);
// Set node descriptions
anim.UpdateNodeDescription(senderNode.Get(0), "Sender");
anim.UpdateNodeDescription(routerNodes.Get(0), "Router1");
anim.UpdateNodeDescription(routerNodes.Get(1), "Router2");
anim.UpdateNodeDescription(receiverNode.Get(0), "Receiver");
// Set node colors
anim.UpdateNodeColor(senderNode.Get(0), 0, 255, 0); // Green
anim.UpdateNodeColor(routerNodes.Get(0), 0, 0, 255); // Blue
anim.UpdateNodeColor(routerNodes.Get(1), 0, 0, 255); // Blue
anim.UpdateNodeColor(receiverNode.Get(0), 255, 0, 0); // Red
// Run simulation
Simulator::Stop(Seconds(30.0));
Simulator::Run();
// Print flow monitor statistics
flowMonitor->CheckForLostPackets();
Ptr<Ipv4FlowClassifier> classifier = DynamicCast<Ipv4FlowClassifier>(flowHelper.GetClassifier());
FlowMonitor::FlowStatsContainer stats = flowMonitor->GetFlowStats();
std::cout << "\n--- Flow Statistics for TCP " << tcpVariant << " ---\n" << std::endl;
for (auto const &flow : stats)
{
Ipv4FlowClassifier::FiveTuple t = classifier->FindFlow(flow.first);
std::cout << "Flow " << flow.first << " (" << t.sourceAddress << " -> " << t.destinationAddress << ")" << std::endl;
std::cout << " Tx Packets: " << flow.second.txPackets << std::endl;
std::cout << " Rx Packets: " << flow.second.rxPackets << std::endl;
std::cout << " Lost Packets: " << flow.second.lostPackets << std::endl;
std::cout << " Packet Loss Ratio: " << (double)flow.second.lostPackets / flow.second.txPackets * 100 << "%" << std::endl;
if (flow.second.rxPackets > 0)
{
double throughput = flow.second.rxBytes * 8.0 /
(flow.second.timeLastRxPacket.GetSeconds() - flow.second.timeFirstTxPacket.GetSeconds()) / 1000000;
std::cout << " Throughput: " << throughput << " Mbps" << std::endl;
std::cout << " Mean Delay: " << flow.second.delaySum.GetSeconds() / flow.second.rxPackets * 1000 << " ms" << std::endl;
std::cout << " Mean Jitter: " << flow.second.jitterSum.GetSeconds() / flow.second.rxPackets * 1000 << " ms" << std::endl;
}
std::cout << std::endl;
}
// Close output files
if (cwndFiles[tcpVariant]->is_open())
{
cwndFiles[tcpVariant]->close();
}
if (throughputFiles[tcpVariant]->is_open())
{
throughputFiles[tcpVariant]->close();
}
delete cwndFiles[tcpVariant];
delete throughputFiles[tcpVariant];
Simulator::Destroy();
}
int main(int argc, char *argv[])
{
// Command line arguments
double packetLossRate = 0.05; // 5% packet loss
std::string variant = "all";
CommandLine cmd(__FILE__);
cmd.AddValue("lossRate", "Packet loss rate (0.0 to 1.0)", packetLossRate);
cmd.AddValue("variant", "TCP variant (Reno/NewReno/Cubic/Vegas/all)", variant);
cmd.Parse(argc, argv);
// Enable logging
LogComponentEnable("TcpVariantsComparison", LOG_LEVEL_INFO);
std::cout << "\n********************************************************" << std::endl;
std::cout << "* TCP Variants Comparison under " << packetLossRate * 100 << "% Packet Loss *" << std::endl;
std::cout << "* Wired Topology: Sender -- R1 -- R2 -- Receiver *" << std::endl;
std::cout << "********************************************************\n" << std::endl;
// List of TCP variants to test
std::vector<std::string> variants;
if (variant == "all")
{
variants = {"Reno", "NewReno", "Cubic", "Vegas"};
}
else
{
variants = {variant};
}
// Run simulation for each variant
for (const auto &v : variants)
{
RunSimulation(v, packetLossRate);
}
// Generate gnuplot script for comparison
std::ofstream gnuplotScript("plot-comparison.plt");
gnuplotScript << "# Gnuplot script for TCP variants comparison\n\n";
// Throughput comparison plot
gnuplotScript << "set terminal png size 1200,800 enhanced font 'Arial,12'\n";
gnuplotScript << "set output 'tcp-throughput-comparison.png'\n";
gnuplotScript << "set title 'TCP Throughput Comparison under 5% Packet Loss'\n";
gnuplotScript << "set xlabel 'Time (seconds)'\n";
gnuplotScript << "set ylabel 'Throughput (Mbps)'\n";
gnuplotScript << "set grid\n";
gnuplotScript << "set key right top\n";
gnuplotScript << "plot 'tcp-Reno-throughput.dat' with lines lw 2 title 'TCP Reno', \\\n";
gnuplotScript << " 'tcp-NewReno-throughput.dat' with lines lw 2 title 'TCP NewReno', \\\n";
gnuplotScript << " 'tcp-Cubic-throughput.dat' with lines lw 2 title 'TCP Cubic', \\\n";
gnuplotScript << " 'tcp-Vegas-throughput.dat' with lines lw 2 title 'TCP Vegas'\n\n";
// Congestion window comparison plot
gnuplotScript << "set output 'tcp-cwnd-comparison.png'\n";
gnuplotScript << "set title 'TCP Congestion Window Comparison under 5% Packet Loss'\n";
gnuplotScript << "set xlabel 'Time (seconds)'\n";
gnuplotScript << "set ylabel 'Congestion Window (bytes)'\n";
gnuplotScript << "set grid\n";
gnuplotScript << "set key right top\n";
gnuplotScript << "plot 'tcp-Reno-cwnd.dat' with lines lw 2 title 'TCP Reno', \\\n";
gnuplotScript << " 'tcp-NewReno-cwnd.dat' with lines lw 2 title 'TCP NewReno', \\\n";
gnuplotScript << " 'tcp-Cubic-cwnd.dat' with lines lw 2 title 'TCP Cubic', \\\n";
gnuplotScript << " 'tcp-Vegas-cwnd.dat' with lines lw 2 title 'TCP Vegas'\n";
gnuplotScript.close();
std::cout << "\n========================================" << std::endl;
std::cout << "Simulation Complete!" << std::endl;
std::cout << "========================================" << std::endl;
std::cout << "\nOutput files generated:" << std::endl;
std::cout << "- Animation files: tcp-<variant>-animation.xml" << std::endl;
std::cout << "- Throughput data: tcp-<variant>-throughput.dat" << std::endl;
std::cout << "- CWND data: tcp-<variant>-cwnd.dat" << std::endl;
std::cout << "- Gnuplot script: plot-comparison.plt" << std::endl;
std::cout << "\nTo generate graphs, run:" << std::endl;
std::cout << " gnuplot plot-comparison.plt" << std::endl;
std::cout << "\nTo view animation, run:" << std::endl;
std::cout << " ./netanim tcp-<variant>-animation.xml" << std::endl;
return 0;
}
Animation Screenshot
|
|
| NetAnim Topology View |
|
|
| NetAnim Packet Flow |
Figure 1: NetAnim Visualization of TCP Communication
This animation shows the wired dumbbell topology with four nodes: Sender (green), Router1 (blue), Router2 (blue), and Receiver (red). The packet flow is visible between the sender and receiver nodes. The bottleneck link between Router1 and Router2 experiences 5% random packet loss, causing TCP to repeatedly detect congestion and adjust its sending rate. The animation clearly shows packet drops occurring at the bottleneck link.
Graph Screenshot
Throughput Comparison Graph
|
|
| Figure 2: TCP Throughput Comparison under 5% Random Packet Loss |
This graph compares the throughput (in Mbps) of all four TCP variants over the 30-second simulation period. Key observations:
- TCP Cubic shows the highest average throughput due to its aggressive window growth algorithm, though with significant fluctuations.
- TCP NewReno demonstrates more stable throughput than Reno due to improved fast recovery.
- TCP Reno exhibits frequent throughput drops as it struggles with multiple packet losses.
- TCP Vegas maintains the most consistent but lower throughput, as its delay-based approach prevents aggressive probing.
Congestion Window Comparison Graph
Figure 3: TCP Congestion Window Evolution under 5% Packet Loss
This graph illustrates the congestion window dynamics of each TCP variant:
- TCP Cubic achieves the largest congestion window but shows sharp sawtooth patterns due to loss detection.
- TCP NewReno maintains moderate window sizes with gradual recovery after losses.
- TCP Reno shows classical AIMD behavior with frequent window reductions.
- TCP Vegas keeps consistently small windows, prioritizing stability over throughput.
Results Summary Table
| TCP Variant | Avg Throughput (Mbps) | Packet Loss Ratio | Mean Delay (ms) | Stability |
|---|---|---|---|---|
| Reno | ~3.2 | ~8% | ~45 | Low |
| NewReno | ~4.1 | ~7% | ~42 | Medium |
| Cubic | ~4.8 | ~9% | ~50 | Medium |
| Vegas | ~2.5 | ~5% | ~35 | High |
Analysis and Conclusions
- Throughput Performance: TCP Cubic achieves the highest throughput due to its aggressive window increase function, but this comes at the cost of higher packet loss rates and delays.
- Loss Recovery: NewReno significantly outperforms Reno in scenarios with multiple packet losses within a single window, demonstrating the importance of improved fast recovery mechanisms.
- Delay-Based vs Loss-Based: TCP Vegas, being delay-based, maintains lower queue occupancy and experiences the lowest actual packet loss. However, it achieves lower throughput as it backs off before the network is fully saturated.
- Fairness Considerations: In mixed environments, Vegas would likely be starved by more aggressive variants like Cubic, highlighting the importance of algorithm selection in shared networks.
- 5% Loss Impact: The 5% random packet loss significantly impacts all loss-based TCP variants, causing them to interpret random losses as congestion signals and repeatedly reduce their sending rates.
Comments
Post a Comment