Performance Comparison of TCP Reno, TCP NewReno, TCP Cubic, and TCP Vegas

TCP Reno, TCP NewReno, TCP Cubic, and TCP Vegas: Performance Comparison Under 5% Random Packet Loss in NS-3

Transmission Control Protocol (TCP) plays a vital role in ensuring reliable data delivery across networks. However, different TCP variants respond differently when packet losses occur. In this experiment, we used the NS-3 network simulator to compare four popular TCP congestion control algorithms—TCP Reno, TCP NewReno, TCP Cubic, and TCP Vegas—under a wired topology with a 5% random packet loss rate.

AI (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 the 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 network.cc and run with ./ns3 run scratch/network.cc

Simulation Setup

The simulation was designed with the following objectives:

  • Create a wired point-to-point topology.
  • Introduce 5% random packet loss using an error model.
  • Evaluate TCP Reno, NewReno, Cubic, and Vegas.
  • Generate NetAnim visualizations.
  • Produce throughput and congestion window data for graph plotting.
  • Analyze throughput, packet loss, delay, and stability metrics.

Network Topology

Sender → Router1 → Router2 → Receiver

  • Access Links: 100 Mbps, 2 ms delay
  • Bottleneck Link: 10 Mbps, 10 ms delay
  • Packet Loss: 5% random loss at the bottleneck link
  • Simulation Duration: 30 seconds
Source code: 
/* -*- 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;
}

Key Findings

1. TCP Cubic Achieves the Highest Throughput

Among all variants, TCP Cubic delivered the highest average throughput. Its aggressive congestion window growth allows it to utilize available bandwidth more efficiently. However, this comes with increased fluctuations and higher packet loss.

2. TCP NewReno Outperforms Reno

TCP NewReno showed better resilience against multiple packet losses within the same congestion window. Its enhanced fast recovery mechanism helped maintain more stable throughput compared to classic Reno.

3. TCP Vegas Prioritises Stability

Unlike the loss-based variants, TCP Vegas uses delay measurements to anticipate congestion. This resulted in:

  • Lower packet loss
  • Reduced delay
  • More stable performance

However, Vegas sacrificed throughput to maintain network stability.

4. Impact of Random Packet Loss

The 5% random packet loss significantly affected loss-based TCP variants. Since packet loss is interpreted as network congestion, Reno, NewReno, and Cubic repeatedly reduced their sending rates, impacting overall performance.

Performance Summary

TCP VariantAvg Throughput (Mbps)Packet Loss RatioMean Delay (ms)Stability
Reno~3.2~8%~45Low
NewReno~4.1~7%~42Medium
Cubic~4.8~9%~50Medium
Vegas~2.5~5%~35High

Values are based on the simulation results generated in NS-3.

Network Animation using NetAnim

Congestion Window Comparison

Throughput comparison




Conclusion

This NS-3 study demonstrates how different TCP congestion control algorithms react under adverse network conditions. TCP Cubic offers the highest throughput, making it suitable for high-speed networks, while TCP Vegas provides superior stability and lower delays. TCP NewReno presents a balanced approach and remains a strong improvement over classic Reno. The results highlight the importance of selecting the appropriate TCP variant based on application requirements and network conditions.

Hashtags

#NS3 #NetworkSimulation #TCP #TCPReno #TCPNewReno #TCPCubic #TCPVegas #ComputerNetworks #CongestionControl #PacketLoss #NetworkPerformance #WirelessNetworks #WiredNetworks #NetworkResearch #PhDResearch #SimulationStudy #Networking #DataCommunication #NetAnim #Gnuplot #ResearchBlog #Engineering #ComputerScience #NetworkEngineering #AcademicResearch #TechBlogging #InternetProtocols #PerformanceAnalysis #OpenSource #NetworkTesting

Comments

Popular posts from this blog

How to Create Ubuntu 24.04 Bootable USB Using Rufus [Step-by-Step Guide]

Installing ns3 in Ubuntu 22.04 | Complete Instructions

Installation of NS2 in Ubuntu 22.04 | NS2 Tutorial 2