A homepage subtitle here And an awesome description here!

15 June 2026

Implement a simple DNS query-response application over UDP sockets in NS3 | NS3 Project 15

Aim:

To design and simulate a DNS query-response mechanism over UDP sockets using NS3. The client sends QUERY:<domain> packets to a DNS server through a router; the server replies with ANSWER:<domain>:<ip> or NXDOMAIN. The experiment measures throughput, delay, and packet delivery ratio, and visualises packet flow using NetAnim and Gnuplot.

Prompt:

"Implement a simple DNS query-response application over UDP sockets in NS3. The simulation should include a DNS Client node, a Router, and a DNS Server node connected via point-to-point links. The client should send DNS queries (QUERY:<domain>) and the server should respond with IP addresses (ANSWER:<domain>:<ip>) or NXDOMAIN. Include NetAnim animation output, FlowMonitor statistics, and Gnuplot graph generation for throughput and delay.

LLM used: Claude (Anthropic), Gemini

Source Code:

#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/netanim-module.h"
#include "ns3/flow-monitor-module.h"
#include "ns3/gnuplot.h"
#include "ns3/mobility-module.h"
#include <string>
#include <map>
#include <vector>
#include <fstream>
#include <iostream>
using namespace ns3;
NS_LOG_COMPONENT_DEFINE("DnsSimulation");

/* --- DNS Server Application --- */
class DnsServerApp : public Application {
public:
DnsServerApp() : m_port(53), m_socket(0), m_queryCount(0) {}
static TypeId GetTypeId() {
static TypeId tid = TypeId("DnsServerApp")
.SetParent<Application>()
.SetGroupName("Tutorial")
.AddConstructor<DnsServerApp>();
return tid;
}

void Setup(uint16_t port) {
m_port = port;
m_dnsTable["www.example.com"] = "93.184.216.34";
m_dnsTable["www.google.com"] = "142.250.64.100";
}
uint32_t GetQueryCount() const { return m_queryCount; }
private:
virtual void StartApplication() {
m_socket = Socket::CreateSocket(GetNode(), UdpSocketFactory::GetTypeId());
m_socket->Bind(InetSocketAddress(Ipv4Address::GetAny(), m_port));
m_socket->SetRecvCallback(MakeCallback(&DnsServerApp::HandleRead, this));
}

void HandleRead(Ptr<Socket> socket) {
Ptr<Packet> packet; Address from;
while ((packet = socket->RecvFrom(from))) {
uint8_t buf[256] = {0};
packet->CopyData(buf, sizeof(buf) - 1);
std::string payload((char*)buf);
if (payload.find("QUERY:") == 0) {
m_queryCount++;
std::string domain = payload.substr(6);
std::string response = "ANSWER:" + domain + ":" + (m_dnsTable.count(domain) ? m_dnsTable[domain] : "NXDOMAIN");
Ptr<Packet> resp = Create<Packet>((const uint8_t*)response.c_str(), response.size());
socket->SendTo(resp, 0, from);
}
}
}

uint16_t m_port; Ptr<Socket> m_socket; std::map<std::string, std::string> m_dnsTable; uint32_t m_queryCount;

};



/* --- DNS Client Application --- */

class DnsClientApp : public Application {
public:
DnsClientApp() : m_socket(0), m_queryIndex(0) {}
static TypeId GetTypeId() {
static TypeId tid = TypeId("DnsClientApp")
.SetParent<Application>()
.SetGroupName("Tutorial")
.AddConstructor<DnsClientApp>();
return tid;
}

void Setup(Ipv4Address addr, uint16_t port) {
m_serverAddr = addr;
m_serverPort = port;
m_domains = {"www.example.com", "www.google.com", "www.ns3sim.net", "www.unknown.org", "mail.example.com"};
}
private:
virtual void StartApplication() {
m_socket = Socket::CreateSocket(GetNode(), UdpSocketFactory::GetTypeId());
m_socket->Connect(InetSocketAddress(m_serverAddr, m_serverPort));
m_sendEvent = Simulator::Schedule(Seconds(1.0), &DnsClientApp::SendQuery, this);
}

void SendQuery() {

if (m_queryIndex < m_domains.size()) {

std::string q = "QUERY:" + m_domains[m_queryIndex++];
m_socket->Send(Create<Packet>((const uint8_t*)q.c_str(), q.size()));
m_sendEvent = Simulator::Schedule(Seconds(1.0), &DnsClientApp::SendQuery, this);
}
}
Ipv4Address m_serverAddr; uint16_t m_serverPort; Ptr<Socket> m_socket; EventId m_sendEvent; std::vector<std::string> m_domains; uint32_t m_queryIndex;
};

/* --- Main Simulation --- */
int main(int argc, char* argv[]) {
CommandLine cmd;
cmd.Parse(argc, argv);
NodeContainer nodes;
nodes.Create(3);

// Mobility
MobilityHelper mobility;
Ptr<ListPositionAllocator> positionAlloc = CreateObject<ListPositionAllocator>();
positionAlloc->Add(Vector(10.0, 50.0, 0.0));
positionAlloc->Add(Vector(50.0, 50.0, 0.0));
positionAlloc->Add(Vector(90.0, 50.0, 0.0));
mobility.SetPositionAllocator(positionAlloc);
mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel");
mobility.Install(nodes);
PointToPointHelper p2p;
p2p.SetDeviceAttribute("DataRate", StringValue("10Mbps"));
p2p.SetChannelAttribute("Delay", StringValue("2ms"));
NetDeviceContainer d01 = p2p.Install(nodes.Get(0), nodes.Get(1));
NetDeviceContainer d12 = p2p.Install(nodes.Get(1), nodes.Get(2));
InternetStackHelper stack;
stack.Install(nodes);
Ipv4AddressHelper address;
address.SetBase("10.1.1.0", "255.255.255.0");
address.Assign(d01);
address.SetBase("10.1.2.0", "255.255.255.0");
Ipv4InterfaceContainer i12 = address.Assign(d12);
Ipv4GlobalRoutingHelper::PopulateRoutingTables();

// NetAnim
AnimationInterface anim("dns-anim.xml");
anim.UpdateNodeDescription(nodes.Get(0), "Client");
anim.UpdateNodeDescription(nodes.Get(1), "Router");
anim.UpdateNodeDescription(nodes.Get(2), "Server");
p2p.EnablePcapAll("dns-trace");

// Applications
Ptr<DnsServerApp> server = CreateObject<DnsServerApp>();
server->Setup(53);
nodes.Get(2)->AddApplication(server);
server->SetStartTime(Seconds(1.0));
Ptr<DnsClientApp> client = CreateObject<DnsClientApp>();
client->Setup(i12.GetAddress(1), 53);
nodes.Get(0)->AddApplication(client);
client->SetStartTime(Seconds(2.0));
FlowMonitorHelper flowmon;
Ptr<FlowMonitor> monitor = flowmon.InstallAll();
Simulator::Stop(Seconds(15.0));
Simulator::Run();

// Gnuplot
Gnuplot plot("throughput.png");
plot.SetTitle("Throughput vs Flow ID");
plot.SetTerminal("png");
Gnuplot2dDataset dataset;
dataset.SetStyle(Gnuplot2dDataset::LINES_POINTS);
monitor->CheckForLostPackets();
Ptr<Ipv4FlowClassifier> classifier = DynamicCast<Ipv4FlowClassifier>(flowmon.GetClassifier());
std::map<FlowId, FlowMonitor::FlowStats> stats = monitor->GetFlowStats();
std::cout << "\n--- Flow Statistics ---" << std::endl;
for (auto it = stats.begin(); it != stats.end(); ++it) {
Ipv4FlowClassifier::FiveTuple t = classifier->FindFlow(it->first);
double duration = it->second.timeLastRxPacket.GetSeconds() - it->second.timeFirstTxPacket.GetSeconds();
double throughput = (duration > 0) ? (it->second.rxBytes * 8.0 / (duration * 1000.0)) : 0;
std::cout << "Flow " << it->first << " (" << t.sourceAddress << " -> " << t.destinationAddress << "): "
<< throughput << " kbps [Rx Packets: " << it->second.rxPackets << "]" << std::endl;
dataset.Add((double)it->first, throughput);
}
plot.AddDataset(dataset);
std::ofstream plotFile("dns-throughput.plt");
plot.GenerateOutput(plotFile);
plotFile.close();
Simulator::Destroy();
return 0;
}

Graph:

The graph plots throughput (in kbps) on the Y-axis against Flow ID on the X-axis. Two flows are recorded — Flow 1 (client → server, DNS query direction) and Flow 2 (server → client, DNS response direction).

  • Flow 1 throughput: ~0.484 kbps
  • Flow 2 throughput: ~0.608 kbps
  • The relationship is linear and increasing from Flow 1 to Flow 2.

 

NetAnim:

Linear 3-node chain - Client connected to Router via P2P, Router to DNS Server via P2P. Animated arrow show DNS query packets traversing the network and response packets returning. All routing via Ipv4GlobalRoutingHelper


WireShark:

 

 


14 June 2026

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

 

 

 


Powered by Blogger.

About Me

Featured Post

5G Network Simulation in NS3 using mmWave | NS3 Tutorial 2024

5G Network Simulation in NS3 Using mmWave This post shows the installation of ns3mmwave in Ubuntu 24.04 and simulates 5G networks in ns3. In...

Contact form

Name

Email *

Message *

Total Pageviews

Search This Blog

Pages

Pages

Pages - Menu

Most Popular

Popular Posts