Skip to main content

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 Topology View
NetAnim Topology View
NetAnim Packet Flow
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

TCP Throughput Comparison
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

TCP Congestion Window Evolution

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

  1. 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.
  2. Loss Recovery: NewReno significantly outperforms Reno in scenarios with multiple packet losses within a single window, demonstrating the importance of improved fast recovery mechanisms.
  3. 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.
  4. 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. 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

Popular posts from this blog

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

How to Create Ubuntu 24.04 Bootable USB Using Rufus [Step-by-Step Guide] Are you planning to install or try Ubuntu 24.04 LTS ? The easiest and most reliable method is to create a bootable USB drive using Rufus on a Windows system. This detailed guide will help you create a Ubuntu 24.04 USB bootloader using Rufus with easy-to-follow steps and screenshots (optional). Here is the complete video of the bootloader creation and OS installation in Windows 11. 🧰 Requirements A USB flash drive (minimum 8GB recommended) A Windows PC Ubuntu 24.04 LTS ISO file Rufus USB creation tool 🧾 Steps to Create a Ubuntu 24.04 Bootable USB Using Rufus ✅ Step 1: Download Ubuntu 24.04 ISO Visit the official Ubuntu website and download the Ubuntu 24.04 LTS ISO file . ✅ Step 2: Download and Run Rufus Head to Rufus official site and download the latest version. Open the executable file (no installation required). ✅ Step 3: Insert USB Drive Plug in your USB drive. Rufus ...

Installing ns3 in Ubuntu 22.04 | Complete Instructions

In this post, we are going to see how to install ns-3.36.1 in Ubuntu 22.04. You can follow the video for complete details Tools used in this simulation: NS3 version ns-3.36.1  OS Used: Ubuntu 22.04 LTS Installation of NS3 (ns-3.36.1) There are some changes in the ns3 installation procedure and the dependencies. So open a terminal and issue the following commands Step 1:  Prerequisites $ sudo apt update In the following packages, all the required dependencies are taken care and you can install all these packages for the complete use of ns3. $ sudo apt install g++ python3 python3-dev pkg-config sqlite3 cmake python3-setuptools git qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools gir1.2-goocanvas-2.0 python3-gi python3-gi-cairo python3-pygraphviz gir1.2-gtk-3.0 ipython3 openmpi-bin openmpi-common openmpi-doc libopenmpi-dev autoconf cvs bzr unrar gsl-bin libgsl-dev libgslcblas0 wireshark tcpdump sqlite sqlite3 libsqlite3-dev  libxml2 libxml2-dev libc6-dev libc6-dev-i386 libc...

NS2 (NS-2.35) Installation in Ubuntu 11.10

This post will help you in installing Network Simulator 2 version NS2.35 in Ubuntu 11.10 Instructions Install Ubuntu Download NS-2.35 ( http://sourceforge.net/projects/nsnam/files/allinone/ns-allinone-2.35/ns-allinone-2.35.tar.gz/download ) Unzip or untar it to any folder (recommended is /home/ loginname) using the following commands one by one sudo apt-get update sudo apt-get install build-essential autoconf automake libxmu-dev tar zxvf ns-allinone-2.35.tar.gz cd ns-allinone-2.35 ./install Once installed the PATH information will be provided to you. Copy the PATH and LD_LIBRARY_PATH Variable to .bashrc (see a dot in the beginning) Input the path information in .bashrc file like this export PATH=$PATH:<Place your paths here> export LD_LIBRARY_PATH=$LD_LIBRARY_PATH: <place the LD_LIBRARY_PATHS> here. Once done, save the file and close execute the command source .bashrc try ns or nam to see whether your installation succeeded.