Skip to main content

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:

  1. 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).

  1. Traffic Configuration

        Use a TCP application (e.g., BulkSendApplication or OnOffApplication).

        Configure a receiver using PacketSink.

        Run simulations for multiple RTT values.

  1. Metrics to Analyze
    Collect and output the following:

        Throughput

        Packet loss

        End-to-end delay

        Congestion window (cwnd) behavior over time

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

  1. NetAnim Visualization

        Integrate NetAnim:

        Generate an XML animation file.

        Ensure node positions and packet flows are visible.

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

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

  1. Code Structure

        Write clean, modular C++ code compatible with ns-3.

        Include comments explaining each section.

        Ensure the code compiles and runs without errors.

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

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

 

 

 

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.