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.
- NetAnim
Visualization
●
Integrate NetAnim:
○
Generate an XML animation file.
○
Ensure node positions and packet
flows are visible.
- 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
// Shared between simulation runs and trace callbacks.
// All global variables are prefixed with g_.
// =============================================================================
/// Per-run congestion-window output stream (opened/closed in RunSimulation)
static std::ofstream g_cwndStream;
/// Cross-run summary file (opened once in main(), closed at the very end)
static std::ofstream g_summaryStream;
/// TCP Maximum Segment Size – used by the cwnd callback to convert bytes→segs
static uint32_t g_segmentSize = 1024; // bytes
// =============================================================================
// TRACE CALLBACKS
// =============================================================================
/**
* CwndChange
* ──────────
* Invoked automatically by the ns-3 tracing subsystem whenever the sender's
* congestion window (CongestionWindow attribute) changes.
*
* Output format (tab-separated, written to g_cwndStream):
* <simulation_time_s> <TAB> <cwnd_in_segments>
*
* Converting bytes → segments gives an intuitive unit for the Gnuplot graph.
*
* @param oldCwnd Previous cwnd value in bytes (unused, required by signature)
* @param newCwnd New cwnd value in bytes
*/
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
// =============================================================================
/**
* ConnectCwndTrace
* ─────────────────
* Called via Simulator::Schedule() ~1 ms after BulkSend::StartApplication()
* so that the TCP socket has been created and appears in SocketList.
*
* Trace path explained:
* /NodeList/0 → sender node (index 0)
* $ns3::TcpL4Protocol → TCP layer object
* /SocketList/0 → first (and only) TCP socket
* /CongestionWindow → traced uint32 attribute
*/
static void
ConnectCwndTrace()
{
Config::ConnectWithoutContext(
"/NodeList/0/$ns3::TcpL4Protocol/SocketList/0/CongestionWindow",
MakeCallback(&CwndChange));
}
// =============================================================================
// FLOWMONITOR REPORTING
// Separated into its own function to keep RunSimulation() readable.
// =============================================================================
/**
* PrintFlowMonitorStats
* ──────────────────────
* Extracts per-flow statistics from FlowMonitor after the simulation ends,
* prints a detailed human-readable report to stdout, exports the full XML
* record to disk, and returns the four summary scalars needed by the
* cross-run summary table.
*
* FlowMonitor statistics used
* ───────────────────────────
* fs.txPackets / rxPackets / lostPackets
* Counters maintained by the FlowProbe installed on each node.
*
* fs.txBytes / rxBytes
* Byte counters including all headers (IP + TCP + payload).
* Throughput = rxBytes * 8 / duration (Mbps).
*
* fs.delaySum
* Sum of (rxTimestamp – txTimestamp) over all received packets.
* Average delay = delaySum / rxPackets.
*
* fs.jitterSum
* Sum of |delay_i – delay_{i-1}| (inter-arrival jitter).
* Mean jitter = jitterSum / (rxPackets – 1).
*
* fs.timeFirstRxPacket / timeLastRxPacket
* Wall-clock time of the first/last packet received by the sink.
* Using this window for throughput avoids the TCP handshake dead time.
*
* @param monitor Active FlowMonitor object (post-simulation)
* @param flowHelper Helper used to retrieve the classifier
* @param port Destination port of the flow of interest
* @param rttMs RTT of this run (for labelling only)
* @param xmlFile Filesystem path for the FlowMonitor XML export
* @param[out] tput Computed throughput in Mbps
* @param[out] loss Computed packet loss ratio [0,1]
* @param[out] delay Computed average delay in ms
* @param[out] jitter Computed mean jitter in ms
*/
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)
{
// Ask FlowMonitor to mark any packets still in-flight as lost
monitor->CheckForLostPackets();
// Export the full XML record (histograms + probe samples + flow stats)
// enableHistograms=true, enableProbes=true
monitor->SerializeToXmlFile(xmlFile, true, true);
std::cout << " FlowMonitor XML → " << xmlFile << "\n";
Ptr<Ipv4FlowClassifier> classifier =
DynamicCast<Ipv4FlowClassifier>(flowHelper.GetClassifier());
FlowMonitor::FlowStatsContainer stats = monitor->GetFlowStats();
// Print a header banner
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);
// Only report the TCP upload flow n0 → n1 (destination port = 9)
if (t.destinationPort != port)
continue;
flowFound = true;
const FlowMonitor::FlowStats& fs = kv.second;
// ── Throughput ────────────────────────────────────────────────────
// Duration between first and last received packet is used so that
// the TCP handshake and FIN exchange do not dilute the measurement.
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);
// ── Goodput ───────────────────────────────────────────────────────
// Same formula but using payload bytes only.
// Approximation: subtract 40 B (IP 20 B + TCP 20 B) per received packet.
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);
// ── Packet loss ───────────────────────────────────────────────────
double lossRatio = 0.0;
if (fs.txPackets > 0)
lossRatio = static_cast<double>(fs.lostPackets)
/ static_cast<double>(fs.txPackets);
// ── Delay statistics ──────────────────────────────────────────────
// ns-3.42 FlowStats provides delaySum (aggregate); min/max are not
// tracked natively. Average delay is derived from the aggregate.
double avgDelayMs = 0.0;
if (fs.rxPackets > 0)
{
avgDelayMs = (fs.delaySum.GetSeconds() * 1000.0)
/ static_cast<double>(fs.rxPackets);
}
// ── Jitter ────────────────────────────────────────────────────────
// FlowMonitor accumulates |delay_i - delay_{i-1}| in jitterSum.
// Mean jitter = jitterSum / (rxPackets - 1) if rxPackets > 1.
double meanJitterMs = 0.0;
if (fs.rxPackets > 1)
meanJitterMs = (fs.jitterSum.GetSeconds() * 1000.0)
/ static_cast<double>(fs.rxPackets - 1);
// ── Console output ────────────────────────────────────────────────
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";
// Return summary scalars to the caller
tput = throughputMbps;
loss = lossRatio;
delay = avgDelayMs;
jitter = meanJitterMs;
}
if (!flowFound)
{
std::cout << " WARNING: no matching flow found (port " << port << ")\n";
}
}
// ====================================================================
// RunSimulation
// ==============================================================
/**
* RunSimulation
* ──────────────
* Builds the entire network, runs the simulation for the given RTT value,
* collects all metrics, and writes all trace/output files for that run.
* Ends with Simulator::Destroy() so the next call starts from a clean slate.
*
* @param rttMs Target RTT in milliseconds (channel delay = RTT / 2)
* @param enablePcap Generate PCAP files for this run (sender + receiver)
* @param enableNetAnim Generate NetAnim XML for this run
* @param resultsDir Output directory path (must already exist)
*/
static void
RunSimulation(uint32_t rttMs,
bool enablePcap,
bool enableNetAnim,
const std::string& resultsDir)
{
// ── Parameters ────────────────────────────────────────────────────────────
const double simTime = 30.0; // simulation duration (seconds)
const uint32_t port = 9; // TCP destination port (discard)
const uint32_t halfRttMs = rttMs / 2; // one-way propagation delay
g_segmentSize = 1024; // TCP MSS (bytes)
std::cout << "\n╔══════════════════════════════════════════════════╗\n"
<< "║ RTT = " << std::setw(4) << rttMs
<< " ms (one-way delay = " << std::setw(3) << halfRttMs
<< " ms) ║\n"
<< "╚══════════════════════════════════════════════════╝\n";
// ── A. TCP / socket defaults ──────────────────────────────────────────────
// Must be set BEFORE any socket objects are instantiated.
// TcpLinuxReno: AIMD – cwnd += 1 MSS per ACK (CA), halved on loss (MD)
Config::SetDefault("ns3::TcpL4Protocol::SocketType",
TypeIdValue(TypeId::LookupByName("ns3::TcpLinuxReno")));
// Classic Reno recovery: enter FR/R on 3 duplicate ACKs, halve cwnd
Config::SetDefault("ns3::TcpL4Protocol::RecoveryType",
TypeIdValue(TypeId::LookupByName("ns3::TcpClassicRecovery")));
// 1 MB send/receive buffers – ensures TCP buffer never limits throughput
Config::SetDefault("ns3::TcpSocket::SndBufSize", UintegerValue(1 << 20));
Config::SetDefault("ns3::TcpSocket::RcvBufSize", UintegerValue(1 << 20));
// Maximum segment size (MSS)
Config::SetDefault("ns3::TcpSocket::SegmentSize", UintegerValue(g_segmentSize));
// Start slow-start with cwnd = 1 for a clear, reproducible trace
Config::SetDefault("ns3::TcpSocket::InitialCwnd", UintegerValue(1));
// ACK every segment (DelAckCount = 1) → finer-grained cwnd trace
Config::SetDefault("ns3::TcpSocket::DelAckCount", UintegerValue(1));
// No SACK – keeps behaviour identical to textbook Reno
Config::SetDefault("ns3::TcpSocketBase::Sack", BooleanValue(false));
// ── B. Create nodes ────────────────────────────────────────────────────────
NodeContainer nodes;
nodes.Create(2);
// nodes.Get(0) → sender (n0)
// nodes.Get(1) → receiver (n1)
// ── C. Point-to-point link ────────────────────────────────────────────────
// RTT = 2 × propagation delay
// ⟹ channel delay = RTT / 2
std::ostringstream delayOss;
delayOss << halfRttMs << "ms";
PointToPointHelper p2p;
p2p.SetDeviceAttribute ("DataRate", StringValue("10Mbps"));
p2p.SetChannelAttribute("Delay", StringValue(delayOss.str()));
// DropTail FIFO queue with 100-packet capacity
p2p.SetQueue("ns3::DropTailQueue", "MaxSize", StringValue("100p"));
NetDeviceContainer devices = p2p.Install(nodes);
// ── D. Internet stack (TCP/IP) ────────────────────────────────────────────
InternetStackHelper internet;
internet.Install(nodes);
// ── E. IP addressing ──────────────────────────────────────────────────────
Ipv4AddressHelper ipv4;
ipv4.SetBase("10.1.1.0", "255.255.255.0");
Ipv4InterfaceContainer interfaces = ipv4.Assign(devices);
// ── F. Applications ───────────────────────────────────────────────────────
// n0 runs BulkSendApplication → saturates the link as fast as TCP allows
// n1 runs PacketSink → absorbs everything that arrives
Address receiverAddr(InetSocketAddress(interfaces.GetAddress(1), port));
// Sender on n0
BulkSendHelper bulkSend("ns3::TcpSocketFactory", receiverAddr);
bulkSend.SetAttribute("MaxBytes", UintegerValue(0)); // unlimited
bulkSend.SetAttribute("SendSize", UintegerValue(g_segmentSize)); // 1 MSS/write
ApplicationContainer senderApps = bulkSend.Install(nodes.Get(0));
senderApps.Start(Seconds(1.0));
senderApps.Stop (Seconds(simTime));
// Receiver on n1
PacketSinkHelper packetSink("ns3::TcpSocketFactory",
InetSocketAddress(Ipv4Address::GetAny(), port));
ApplicationContainer sinkApps = packetSink.Install(nodes.Get(1));
sinkApps.Start(Seconds(0.5)); // starts before the sender
sinkApps.Stop (Seconds(simTime + 1.0));
// ── G. Congestion-window trace ────────────────────────────────────────────
// Open the per-run data file and schedule the trace connection.
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";
// BulkSend::StartApplication() fires at t = 1.0 s and creates the socket.
// Schedule the trace hook 1 ms later so the socket is in SocketList[0].
Simulator::Schedule(Seconds(1.001), &ConnectCwndTrace);
// ── H. FlowMonitor ────────────────────────────────────────────────────────
// FlowMonitor installs probes on every node to intercept packets at both
// the Tx and Rx sides of each IP layer, recording timestamps, byte counts,
// and sequence numbers. Statistics are accumulated until the simulation
// ends, then extracted in section M below.
FlowMonitorHelper flowHelper;
Ptr<FlowMonitor> monitor = flowHelper.InstallAll();
// ── I. ASCII trace (.tr) ──────────────────────────────────────────────────
// One file per RTT; records every enqueue/dequeue/drop event on the link.
// Useful for manual inspection or awk/grep post-processing.
AsciiTraceHelper ascii;
std::string trFilename = resultsDir + "/tcp-reno-rtt-"
+ std::to_string(rttMs) + "ms.tr";
p2p.EnableAsciiAll(ascii.CreateFileStream(trFilename));
// ── J. PCAP trace ─────────────────────────────────────────────────────────
// Generated only for the first run (RTT = 10 ms) to keep disk usage low.
// Files: tcp-reno-rtt-0-0.pcap (sender), tcp-reno-rtt-0-1.pcap (receiver)
//
// How to open:
// wireshark scratch/results/tcp-reno-rtt-0-0.pcap
//
// Useful Wireshark display filters:
// tcp – show all TCP segments
// tcp.flags.syn == 1 – handshake only
// tcp.analysis.retransmission – retransmissions
// tcp.analysis.duplicate_ack – duplicate ACKs (precede loss detection)
if (enablePcap)
{
std::string pcapPrefix = resultsDir + "/tcp-reno-rtt";
p2p.EnablePcapAll(pcapPrefix, false); // false = non-promiscuous
}
// ── K. NetAnim ────────────────────────────────────────────────────────────
// Generated only for the first run to avoid huge XML files.
//
// How to open:
// 1. cd /path/to/ns-allinone-3.42/netanim-3.109
// 2. ./NetAnim
// 3. File → Open → scratch/results/tcp-reno-netanim.xml
// 4. Press ▶ to animate packet flows between n0 and n1
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); // sender – left
anim->SetConstantPosition(nodes.Get(1), 70.0, 30.0); // receiver – right
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);
// Store packet metadata (UID, size, timestamps) for detailed replay
anim->EnablePacketMetadata(true);
}
// ── L. Run ────────────────────────────────────────────────────────────────
Simulator::Stop(Seconds(simTime + 2.0));
Simulator::Run();
// ── M. Collect & report FlowMonitor statistics ────────────────────────────
// Build the FlowMonitor XML filename for this 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);
// ── N. Write one row to the cross-run summary file ────────────────────────
g_summaryStream << std::fixed << std::setprecision(6)
<< rttMs << "\t"
<< throughputMbps << "\t"
<< lossRatio << "\t"
<< avgDelayMs << "\t"
<< meanJitterMs << "\n";
g_summaryStream.flush();
// ── O. Cleanup ────────────────────────────────────────────────────────────
g_cwndStream.close();
// AnimationInterface must be destroyed before Simulator::Destroy()
if (anim)
{
delete anim;
anim = nullptr;
}
// Simulator::Destroy() tears down all ns-3 global state (nodes, devices,
// channels, event queue) so the next RunSimulation() call is independent.
Simulator::Destroy();
}
// =============================================================================
// main
// =============================================================================
int
main(int argc, char* argv[])
{
// ── RTT sweep configuration ───────────────────────────────────────────────
// Add or remove values freely. Each entry costs one full simulation run.
std::vector<uint32_t> rttValues = {10, 25, 50, 75, 100, 125, 150, 175, 200};
// Output directory (relative to the ns-3 root where ./ns3 run is invoked)
const std::string resultsDir = "scratch/results";
// ── Create output directory ───────────────────────────────────────────────
// POSIX mkdir – silently fails if the directory already exists (EEXIST)
::mkdir(resultsDir.c_str(), 0755);
// ── Open cross-run summary file ───────────────────────────────────────────
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;
}
// Column header – load with: gnuplot 'summary.dat' using 1:2
g_summaryStream
<< "# RTT_ms\tThroughput_Mbps\tLossRatio\tAvgDelay_ms\tMeanJitter_ms\n";
// ── Print banner ──────────────────────────────────────────────────────────
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";
// ── Main sweep loop ───────────────────────────────────────────────────────
for (std::size_t i = 0; i < rttValues.size(); ++i)
{
const bool firstRun = (i == 0);
RunSimulation(rttValues[i],
firstRun, // enablePcap : PCAP only for RTT = 10 ms
firstRun, // enableNetAnim : NetAnim only for RTT = 10 ms
resultsDir);
}
g_summaryStream.close();
// ── Print the final summary table ─────────────────────────────────────────
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 gnu-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 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 200ms, 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
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
