Performance Analysis of TCP Reno Congestion Control Under Varying Round-Trip Time Conditions | NS3 Project 14
Performance Analysis of TCP Reno Congestion Control Under Varying Round-Trip Time Conditions: An ns-3 Simulation Study
PROMPT
Part 1:
You are an expert in ns-3 network simulation and C++.
I want you to design and implement a complete ns-3 simulation in C++ to analyze the performance of TCP Reno over a point-to-point link with varying RTT values (10 ms to 200 ms).
Requirements:
-
Simulation Setup
- Create a point-to-point network topology (2 nodes).
- Configure link parameters such as:
- Data rate (choose a reasonable default like 5 Mbps or 10 Mbps)
- Packet size
- Queue type (DropTail or similar)
- Use TCP Reno as the transport protocol.
- Ensure RTT variation is achieved by modifying propagation delay (range: 10 ms to 200 ms in steps).
-
Traffic Configuration
- Use a TCP application (e.g., BulkSendApplication or OnOffApplication).
- Configure a receiver using PacketSink.
- Run simulations for multiple RTT values.
-
Metrics to Analyze
Collect and output the following:- Throughput
- Packet loss
- End-to-end delay
- Congestion window (cwnd) behavior over time
-
Tracing and Logging
- Enable trace files for:
- Congestion window (cwnd)
- Packet drops
- Throughput
- Generate a trace matrix (trace files/logs) that can be used for analysis.
- Enable trace files for:
-
NetAnim Visualization
- Integrate NetAnim:
- Generate an XML animation file.
- Ensure node positions and packet flows are visible.
- Integrate NetAnim:
-
PCAP / Wireshark Support
- Enable PCAP tracing so that the simulation output can be analyzed in Wireshark.
- Clearly mention how to open and inspect the generated PCAP files.
-
Graph Generation (Gnuplot)
- Generate output files compatible with Gnuplot.
- Provide scripts or instructions to plot:
- RTT vs Throughput
- RTT vs Packet Loss
- Time vs Congestion Window
-
Code Structure
- Write clean, modular C++ code compatible with ns-3.
- Include comments explaining each section.
- Ensure the code compiles and runs without errors.
-
Execution Instructions
After writing the code, provide:- Steps to compile and run the simulation in ns-3
- Commands to enable tracing
- Steps to view:
- NetAnim visualization
- Wireshark (PCAP files)
- Gnuplot graphs
-
Expected Output Explanation
- Briefly explain what trends are expected when RTT increases (e.g., effect on throughput and cwnd).
Part 2:
You are an expert in ns-3 and C++.
I have already implemented a TCP Reno RTT analysis simulation in ns-3. Now I want you to update and refine the existing implementation with the following changes:
1. Integrate FlowMonitor
Enhance the simulation by adding FlowMonitor to collect detailed performance metrics.
Include FlowMonitor module: #include "ns3/flow-monitor-module.h"
- Install FlowMonitor on all nodes.
- Collect and compute:
- Throughput per flow
- Packet loss
- Delay statistics
- Output results in a readable format (console + optional XML file).
2. Output Enhancements
- Ensure FlowMonitor results are:
- Printed clearly after simulation ends
- Optionally exported to an XML file (e.g., flowmon.xml)
3. Maintain Existing Features
Make sure the following features remain intact:
- RTT variation (10 ms to 200 ms)
- NetAnim XML generation
- PCAP tracing (for Wireshark)
- Trace files (cwnd, drops, etc.)
- Gnuplot-compatible output
4. Code Quality
- Keep the code modular and well-commented
- Avoid redundancy while integrating FlowMonitor
- Ensure compatibility with standard ns-3 versions
5. Execution Instructions Update
Update the run instructions to reflect:
- New filename (
tcp-reno-rtt-analysis.cc) - FlowMonitor output usage
- Any new compilation flags if required
CODE:
/* ===================================================================
* TCP Reno Performance Analysis over a Point-to-Point Link with Varying RTT
* ns-3 version : 3.42
* Author ID : 24BPS1135
* ─────────────────────────────────────────────────────────────────────
* TOPOLOGY (one instance per simulation run)
* ─────────────────────────────────────────────────────────────────────
* n0 (BulkSend) ──────[P2P 10 Mbps | delay = RTT/2]────── n1 (PacketSink)
* ────────────────────────────────────────────────────────────────────
* WHAT THIS SIMULATION DOES
* ─────────────────────────────────────────────────────────────────────
* Sweeps RTT values {10, 25, 50, 75, 100, 125, 150, 175, 200} ms by running
* 9 back-to-back ns-3 simulations. Each run collects:
*
* Via FlowMonitor
* • Throughput (Mbps)
* • Goodput (Mbps) – application-layer useful bytes / time
* • Packet loss ratio
* • Average / min / max end-to-end delay (ms)
* • Mean jitter (ms)
* • Tx / Rx packet & byte counts
*
* Via trace callbacks
* • Congestion window over time → cwnd_rtt_Xms.dat
* ────────────────────────────────────────────────────────────────────
* OUTPUT FILES (all written to scratch/results/)
* ─────────────────────────────────────────────────────────────────────
* summary.dat RTT | Throughput | Goodput | Loss | Delay | Jitter
* cwnd_rtt_Xms.dat Time(s) | cwnd(segments) – one file per RTT
* flowmon_rtt_Xms.xml FlowMonitor XML export – one file per RTT
* tcp-reno-rtt-Xms.tr ASCII link-level trace – one file per RTT
* tcp-reno-rtt-0-0.pcap Sender PCAP (RTT = 10 ms only)
* tcp-reno-rtt-0-1.pcap Receiver PCAP (RTT = 10 ms only)
* tcp-reno-netanim.xml NetAnim animation (RTT = 10 ms only)
* ────────────────────────────────────────────────────────────────────
* COMPILE & RUN (from the ns-3 root directory)
* ────────────────────────────────────────────────────────────────────
* mkdir -p scratch/results
* ./ns3 build scratch/24BPS1135
* ./ns3 run scratch/24BPS1135
*
* VISUALISE
* Wireshark : wireshark scratch/results/tcp-reno-rtt-0-0.pcap
* NetAnim : ./netanim-3.109/NetAnim → open tcp-reno-netanim.xml
* FlowMonitor: open scratch/results/flowmon_rtt_Xms.xml in any XML viewer
* Gnuplot : gnuplot scratch/plot_throughput.plt
* gnuplot scratch/plot_loss.plt
* gnuplot scratch/plot_cwnd.plt
* ===================================================================== */
// ── Standard library ──────────────────────────────────────────────────────────
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <sys/stat.h> // POSIX mkdir(2)
// ── ns-3 modules ──────────────────────────────────────────────────────────────
#include "ns3/applications-module.h"
#include "ns3/core-module.h"
#include "ns3/flow-monitor-module.h" // FlowMonitor – per-flow statistics
#include "ns3/internet-module.h"
#include "ns3/netanim-module.h" // NetAnim XML animation
#include "ns3/network-module.h"
#include "ns3/point-to-point-module.h"
#include "ns3/traffic-control-module.h"
using namespace ns3;
NS_LOG_COMPONENT_DEFINE("24BPS1135");
// =============================================================================
// GLOBAL STATE
// =============================================================================
static std::ofstream g_cwndStream;
static std::ofstream g_summaryStream;
static uint32_t g_segmentSize = 1024; // bytes
// =============================================================================
// TRACE CALLBACKS
// =============================================================================
static void
CwndChange(uint32_t /* oldCwnd */, uint32_t newCwnd)
{
g_cwndStream << std::fixed << std::setprecision(6)
<< Simulator::Now().GetSeconds() << "\t"
<< static_cast<double>(newCwnd) / g_segmentSize << "\n";
}
// =============================================================================
// HELPER: connect the cwnd trace source after the TCP socket exists
// =============================================================================
static void
ConnectCwndTrace()
{
Config::ConnectWithoutContext(
"/NodeList/0/$ns3::TcpL4Protocol/SocketList/0/CongestionWindow",
MakeCallback(&CwndChange));
}
// =============================================================================
// FLOWMONITOR REPORTING
// =============================================================================
static void
PrintFlowMonitorStats(Ptr<FlowMonitor> monitor,
FlowMonitorHelper& flowHelper,
uint32_t port,
uint32_t rttMs,
const std::string& xmlFile,
double& tput,
double& loss,
double& delay,
double& jitter)
{
monitor->CheckForLostPackets();
monitor->SerializeToXmlFile(xmlFile, true, true);
std::cout << " FlowMonitor XML → " << xmlFile << "\n";
Ptr<Ipv4FlowClassifier> classifier =
DynamicCast<Ipv4FlowClassifier>(flowHelper.GetClassifier());
FlowMonitor::FlowStatsContainer stats = monitor->GetFlowStats();
std::cout << "\n"
<< " ┌─────────────────────────────────────────────────┐\n"
<< " │ FlowMonitor Report – RTT = " << std::setw(4) << rttMs
<< " ms │\n"
<< " └─────────────────────────────────────────────────┘\n";
bool flowFound = false;
for (auto& kv : stats)
{
Ipv4FlowClassifier::FiveTuple t = classifier->FindFlow(kv.first);
if (t.destinationPort != port)
continue;
flowFound = true;
const FlowMonitor::FlowStats& fs = kv.second;
double durationSec = (fs.timeLastRxPacket - fs.timeFirstRxPacket).GetSeconds();
double throughputMbps = 0.0;
if (durationSec > 0.0)
throughputMbps = (static_cast<double>(fs.rxBytes) * 8.0) / (durationSec * 1.0e6);
const uint32_t hdrBytes = 40;
double payloadBytes = (fs.rxBytes > fs.rxPackets * hdrBytes)
? static_cast<double>(fs.rxBytes - fs.rxPackets * hdrBytes)
: static_cast<double>(fs.rxBytes);
double goodputMbps = 0.0;
if (durationSec > 0.0)
goodputMbps = (payloadBytes * 8.0) / (durationSec * 1.0e6);
double lossRatio = 0.0;
if (fs.txPackets > 0)
lossRatio = static_cast<double>(fs.lostPackets) / static_cast<double>(fs.txPackets);
double avgDelayMs = 0.0;
if (fs.rxPackets > 0)
avgDelayMs = (fs.delaySum.GetSeconds() * 1000.0) / static_cast<double>(fs.rxPackets);
double meanJitterMs = 0.0;
if (fs.rxPackets > 1)
meanJitterMs = (fs.jitterSum.GetSeconds() * 1000.0) / static_cast<double>(fs.rxPackets - 1);
std::cout << std::fixed
<< " Flow ID : " << kv.first << "\n"
<< " Src → Dst : "
<< t.sourceAddress << ":" << t.sourcePort << " → "
<< t.destinationAddress << ":" << t.destinationPort << "\n"
<< " Protocol : "
<< (t.protocol == 6 ? "TCP" : "UDP") << "\n"
<< " ─── Packet counters ──────────────────────────────\n"
<< " Tx packets : " << fs.txPackets << "\n"
<< " Rx packets : " << fs.rxPackets << "\n"
<< " Lost packets : " << fs.lostPackets
<< " (" << std::setprecision(4) << lossRatio * 100.0 << " %)\n"
<< " Tx bytes : " << fs.txBytes << "\n"
<< " Rx bytes : " << fs.rxBytes << "\n"
<< " ─── Throughput ───────────────────────────────────\n"
<< " Throughput : " << std::setprecision(4)
<< throughputMbps << " Mbps\n"
<< " Goodput : " << std::setprecision(4)
<< goodputMbps << " Mbps\n"
<< " ─── Delay (end-to-end) ───────────────────────────\n"
<< " Avg delay : " << std::setprecision(3)
<< avgDelayMs << " ms\n"
<< " (min/max per-pkt delay not tracked by ns-3 FlowStats)\n"
<< " ─── Jitter ───────────────────────────────────────\n"
<< " Mean jitter : " << std::setprecision(3)
<< meanJitterMs << " ms\n";
tput = throughputMbps;
loss = lossRatio;
delay = avgDelayMs;
jitter = meanJitterMs;
}
if (!flowFound)
{
std::cout << " WARNING: no matching flow found (port " << port << ")\n";
}
}
// =============================================================================
// RunSimulation
// =============================================================================
static void
RunSimulation(uint32_t rttMs,
bool enablePcap,
bool enableNetAnim,
const std::string& resultsDir)
{
const double simTime = 30.0;
const uint32_t port = 9;
const uint32_t halfRttMs = rttMs / 2;
g_segmentSize = 1024;
std::cout << "\n╔══════════════════════════════════════════════════╗\n"
<< "║ RTT = " << std::setw(4) << rttMs
<< " ms (one-way delay = " << std::setw(3) << halfRttMs
<< " ms) ║\n"
<< "╚══════════════════════════════════════════════════╝\n";
Config::SetDefault("ns3::TcpL4Protocol::SocketType",
TypeIdValue(TypeId::LookupByName("ns3::TcpLinuxReno")));
Config::SetDefault("ns3::TcpL4Protocol::RecoveryType",
TypeIdValue(TypeId::LookupByName("ns3::TcpClassicRecovery")));
Config::SetDefault("ns3::TcpSocket::SndBufSize", UintegerValue(1 << 20));
Config::SetDefault("ns3::TcpSocket::RcvBufSize", UintegerValue(1 << 20));
Config::SetDefault("ns3::TcpSocket::SegmentSize", UintegerValue(g_segmentSize));
Config::SetDefault("ns3::TcpSocket::InitialCwnd", UintegerValue(1));
Config::SetDefault("ns3::TcpSocket::DelAckCount", UintegerValue(1));
Config::SetDefault("ns3::TcpSocketBase::Sack", BooleanValue(false));
NodeContainer nodes;
nodes.Create(2);
std::ostringstream delayOss;
delayOss << halfRttMs << "ms";
PointToPointHelper p2p;
p2p.SetDeviceAttribute ("DataRate", StringValue("10Mbps"));
p2p.SetChannelAttribute("Delay", StringValue(delayOss.str()));
p2p.SetQueue("ns3::DropTailQueue", "MaxSize", StringValue("100p"));
NetDeviceContainer devices = p2p.Install(nodes);
InternetStackHelper internet;
internet.Install(nodes);
Ipv4AddressHelper ipv4;
ipv4.SetBase("10.1.1.0", "255.255.255.0");
Ipv4InterfaceContainer interfaces = ipv4.Assign(devices);
Address receiverAddr(InetSocketAddress(interfaces.GetAddress(1), port));
BulkSendHelper bulkSend("ns3::TcpSocketFactory", receiverAddr);
bulkSend.SetAttribute("MaxBytes", UintegerValue(0));
bulkSend.SetAttribute("SendSize", UintegerValue(g_segmentSize));
ApplicationContainer senderApps = bulkSend.Install(nodes.Get(0));
senderApps.Start(Seconds(1.0));
senderApps.Stop (Seconds(simTime));
PacketSinkHelper packetSink("ns3::TcpSocketFactory",
InetSocketAddress(Ipv4Address::GetAny(), port));
ApplicationContainer sinkApps = packetSink.Install(nodes.Get(1));
sinkApps.Start(Seconds(0.5));
sinkApps.Stop (Seconds(simTime + 1.0));
std::ostringstream cwndFilename;
cwndFilename << resultsDir << "/cwnd_rtt_" << rttMs << "ms.dat";
g_cwndStream.open(cwndFilename.str());
g_cwndStream << "# Time(s)\tcwnd(segments) [RTT=" << rttMs << "ms]\n";
Simulator::Schedule(Seconds(1.001), &ConnectCwndTrace);
FlowMonitorHelper flowHelper;
Ptr<FlowMonitor> monitor = flowHelper.InstallAll();
AsciiTraceHelper ascii;
std::string trFilename = resultsDir + "/tcp-reno-rtt-" + std::to_string(rttMs) + "ms.tr";
p2p.EnableAsciiAll(ascii.CreateFileStream(trFilename));
if (enablePcap)
{
std::string pcapPrefix = resultsDir + "/tcp-reno-rtt";
p2p.EnablePcapAll(pcapPrefix, false);
}
AnimationInterface* anim = nullptr;
if (enableNetAnim)
{
std::string animFilename = resultsDir + "/tcp-reno-netanim.xml";
anim = new AnimationInterface(animFilename);
anim->SetConstantPosition(nodes.Get(0), 10.0, 30.0);
anim->SetConstantPosition(nodes.Get(1), 70.0, 30.0);
anim->UpdateNodeDescription(nodes.Get(0), "n0 Sender (BulkSend)");
anim->UpdateNodeDescription(nodes.Get(1), "n1 Receiver (PacketSink)");
anim->UpdateNodeSize(nodes.Get(0)->GetId(), 4.0, 4.0);
anim->UpdateNodeSize(nodes.Get(1)->GetId(), 4.0, 4.0);
anim->EnablePacketMetadata(true);
}
Simulator::Stop(Seconds(simTime + 2.0));
Simulator::Run();
std::string xmlFile = resultsDir + "/flowmon_rtt_" + std::to_string(rttMs) + "ms.xml";
double throughputMbps = 0.0;
double lossRatio = 0.0;
double avgDelayMs = 0.0;
double meanJitterMs = 0.0;
PrintFlowMonitorStats(monitor, flowHelper, port, rttMs, xmlFile, throughputMbps, lossRatio, avgDelayMs, meanJitterMs);
g_summaryStream << std::fixed << std::setprecision(6)
<< rttMs << "\t"
<< throughputMbps << "\t"
<< lossRatio << "\t"
<< avgDelayMs << "\t"
<< meanJitterMs << "\n";
g_summaryStream.flush();
g_cwndStream.close();
if (anim)
{
delete anim;
anim = nullptr;
}
Simulator::Destroy();
}
// =============================================================================
// main
// =============================================================================
int
main(int argc, char* argv[])
{
std::vector<uint32_t> rttValues = {10, 25, 50, 75, 100, 125, 150, 175, 200};
const std::string resultsDir = "scratch/results";
::mkdir(resultsDir.c_str(), 0755);
const std::string summaryPath = resultsDir + "/summary.dat";
g_summaryStream.open(summaryPath);
if (!g_summaryStream.is_open())
{
std::cerr << "ERROR: cannot open " << summaryPath << "\n"
<< "Ensure scratch/results/ exists and is writable.\n";
return 1;
}
g_summaryStream << "# RTT_ms\tThroughput_Mbps\tLossRatio\tAvgDelay_ms\tMeanJitter_ms\n";
std::cout << "╔══════════════════════════════════════════════════════════╗\n"
<< "║ TCP Reno RTT Analysis – ns-3 v3.42 ║\n"
<< "║ ID: 24BPS1135 ║\n"
<< "╠══════════════════════════════════════════════════════════╣\n"
<< "║ Topology n0 (BulkSend) ──[P2P 10 Mbps]── n1 (Sink) ║\n"
<< "║ TCP TcpLinuxReno (classic AIMD) ║\n"
<< "║ MSS " << std::setw(4) << g_segmentSize << " bytes ║\n"
<< "║ Sim time 30 s per run ║\n"
<< "║ RTT values 10 25 50 75 100 125 150 175 200 ms ║\n"
<< "║ Results " << resultsDir << "/ ║\n"
<< "╚══════════════════════════════════════════════════════════╝\n";
for (std::size_t i = 0; i < rttValues.size(); ++i)
{
const bool firstRun = (i == 0);
RunSimulation(rttValues[i], firstRun, firstRun, resultsDir);
}
g_summaryStream.close();
std::cout << "\n╔══════════════════════════════════════════════════════════════════════╗\n"
<< "║ FINAL RESULTS SUMMARY ║\n"
<< "╠══════════════════════════════════════════════════════════════════════╣\n"
<< "║ "
<< std::left << std::setw(9) << "RTT(ms)"
<< std::setw(18) << "Throughput(Mbps)"
<< std::setw(12) << "Loss Ratio"
<< std::setw(15) << "Avg Delay(ms)"
<< std::setw(15) << "Jitter(ms)"
<< " ║\n"
<< "╠══════════════════════════════════════════════════════════════════════╣\n";
std::ifstream fin(summaryPath);
std::string line;
while (std::getline(fin, line))
{
if (line.empty() || line[0] == '#')
continue;
std::istringstream iss(line);
uint32_t rtt;
double tp, lr, ad, jt;
if (!(iss >> rtt >> tp >> lr >> ad >> jt))
continue;
std::cout << "║ "
<< std::left << std::setw(9) << rtt
<< std::fixed << std::setprecision(4)
<< std::setw(18) << tp
<< std::setprecision(6)
<< std::setw(12) << lr
<< std::setprecision(3)
<< std::setw(15) << ad
<< std::setw(15) << jt
<< " ║\n";
}
std::cout << "╚══════════════════════════════════════════════════════════════════════╝\n\n"
<< "╔══════════════════════════════════════════════════════════════════════╗\n"
<< "║ OUTPUT FILES ║\n"
<< "╠══════════════════════════════════════════════════════════════════════╣\n"
<< "║ summary.dat RTT, Throughput, Loss, Delay, Jitter ║\n"
<< "║ cwnd_rtt_Xms.dat Congestion window trace (9 files) ║\n"
<< "║ flowmon_rtt_Xms.xml FlowMonitor XML export (9 files) ║\n"
<< "║ tcp-reno-rtt-Xms.tr ASCII link traces (9 files) ║\n"
<< "║ tcp-reno-rtt-0-0.pcap Sender PCAP (RTT=10ms, Wireshark) ║\n"
<< "║ tcp-reno-rtt-0-1.pcap Receiver PCAP (RTT=10ms, Wireshark) ║\n"
<< "║ tcp-reno-netanim.xml NetAnim animation (RTT=10ms) ║\n"
<< "╠══════════════════════════════════════════════════════════════════════╣\n";
return 0;
}
Output :
NetAnim:
NetAnim Visualization (tcp-reno-netanim.xml)
NetAnim displays two nodes — n0 (Sender) on the left and n1 (Receiver) on the right — connected by a link, with animated dots representing packets flowing between them during the simulation. It provides a visual confirmation that data transmission is occurring correctly and that the TCP connection is active between the two endpoints.
Comparison Data File
This .dat file is a referral file to plot the Gnuplot graphs:
Gnuplot Graphs
RTT vs Packet Loss Graph (loss_vs_rtt.png)
This graph plots RTT against the packet loss ratio, showing a gradual upward trend as RTT increases. Higher RTT causes a larger Bandwidth-Delay Product, which overflows the fixed-size DropTail queue more easily, resulting in more frequent packet drops.
RTT vs Throughput Graph (throughput_vs_rtt.png)
This graph plots RTT (x-axis) against achieved throughput in Mbps (y-axis), showing a downward curve as RTT increases. It demonstrates that TCP Reno becomes progressively less efficient at higher RTTs because the congestion window grows slower when ACKs take longer to return.
Congestion Window over Time Graph (cwnd_over_time.png)
At lower RTT the window climbs steeply and reaches a high steady state, while at higher RTT the growth is gradual and resets happen more frequently, keeping cwnd chronically low.
TraceMetrics Result for 10, 100 and 200 ms, Respectively
The trace metrics comparison illustrates that increasing RTT leads to reduced throughput, higher packet loss, and increased end-to-end delay due to slower congestion window growth and higher Bandwidth-Delay Product in TCP Reno.
Wireshark Analysis
The image below shows the TCP three-way handshake (SYN, SYN-ACK, ACK) followed by continuous data transmission between 10.1.1.1 (sender) and 10.1.1.2 (receiver), confirming successful connection establishment and active TCP Reno data flow.
The image below displays Wireshark’s Expert Information, highlighting events such as duplicate ACKs, retransmissions, and out-of-order packets, which indicate congestion and TCP Reno’s loss recovery mechanisms.
The graph below shows packet flow over time along with TCP errors, illustrating fluctuations in transmission rate and the occurrence of congestion-related issues such as retransmissions during the simulation.
Comments
Post a Comment