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:
1. Create a point-to-point wired network topology
2. Implement 5% random packet loss using error model
3. Run simulations for all four TCP variants
4. Generate NetAnim XML file for visualization
5. Output throughput data for gnuplot graphs
6. 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 |
| netanim |
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