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
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

  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