A Discrete-Event Network Simulator
API
Loading...
Searching...
No Matches
fack-example.cc
Go to the documentation of this file.
1/*
2 * Copyright (c) 2018-20, 2025 NITK Surathkal
3 *
4 * SPDX-License-Identifier: GPL-2.0-only
5 *
6 * Authors: Aarti Nandagiri <aarti.nandagiri@gmail.com>
7 * Vivek Jain <jain.vivek.anand@gmail.com>
8 * Mohit P. Tahiliani <tahiliani@nitk.edu.in>
9 *
10 * Modified for FACK by: Jayesh Akot <akotjayesh@gmail.com>
11 */
12
13/**
14 * @file
15 * @ingroup examples-tcp
16 * @brief Example to verify TCP FACK performance based on Mathis & Mahdavi paper.
17 *
18 * The network topology used in this example is based on the Fig. 1 described in
19 * Mathis, M., & Mahdavi, J. (1996, August).
20 * Forward acknowledgement: Refining TCP congestion control.
21 *
22 * @code{.text}
23 * 10 Mbps 1.5 Mbps 10 Mbps
24 * Sender -------------- R1 -------------- R2 -------------- Receiver
25 * 2ms 5ms 33ms
26 * @endcode
27 *
28 * This program runs by default for 100 seconds and creates a new directory
29 * called 'fack-results' in the ns-3 root directory. The program creates one
30 * sub-directory called 'pcap' in 'fack-results' directory (if pcap generation
31 * is enabled) and six .dat files.
32 *
33 * - 'pcap' sub-directory contains six PCAP files:
34 * - fack-0-0.pcap for the interface on Sender
35 * - fack-1-0.pcap for the interface on Receiver
36 * - fack-2-0.pcap for the first interface on R1
37 * - fack-2-1.pcap for the second interface on R1
38 * - fack-3-0.pcap for the first interface on R2
39 * - fack-3-1.pcap for the second interface on R2
40 * - cwnd.dat file contains congestion window trace for the sender node
41 * - throughput.dat file contains sender side throughput trace (throughput is in Mbit/s)
42 * - queueSize.dat file contains queue length trace from the bottleneck link
43 * - lostSegments.dat file contains packet lost trace at queue
44 * - bytesInFlight.dat contains trace of inflight data(SND.NXT - SND.UNA).
45 * - fackAwnd.dat contains trace of the FACK-specific inflight estimate.
46 *
47 * This example allows the user to quantitatively verify two key mechanisms
48 * described in the Mathis & Mahdavi paper:
49 * -# The preservation of the TCP Self-Clock during recovery (Section 4.2).
50 * -# The "Overdamping" mechanism to drain queues after overshoot (Section 4.4).
51 *
52 * To observe these insights, you must run the simulation twice with different configurations:
53 *
54 * - **Step 1:** Run with FACK enabled (Default)
55 * @code ./ns3 run "fack-example --fack=true" @endcode
56 *
57 * - **Step 2:** Run with FACK disabled (Standard Reno+SACK behavior)
58 * @code ./ns3 run "fack-example --fack=false" @endcode
59 *
60 * - **Step 3:** Compare the output traces 'bytesInFlight.dat' and 'cwnd.dat' from both runs.
61 *
62 * **SPECIFIC OBSERVATIONS:**
63 *
64 * **A. Preservation of Self-Clock (Compare 'bytesInFlight.dat'):**
65 * - WITHOUT FACK: When multiple packet losses occur (approx t=0.5s), the
66 * inflight data drops significantly (often stalling near zero). This indicates
67 * a loss of the ACK clock, matching the behavior of "Reno+SACK" described
68 * in Section 4.2 and Figure 3 of the paper.
69 * - WITH FACK: The inflight data remains elevated even during recovery.
70 * This demonstrates that FACK successfully decouples congestion control
71 * from data recovery, using SACK blocks to inject new data and maintain
72 * the self-clock. This matches the behavior in Figure 4 of the paper.
73 *
74 * **B. Overdamping and Queue Draining (Compare 'cwnd.dat' vs 'bytesInFlight.dat'):**
75 * - In the FACK run (between t=1.0s and t=2.0s), observe that 'cwnd' drops
76 * to a low value (~10 packets) while 'bytesInFlight' remains high (~50 packets).
77 * - This divergence is the "Overdamping" mechanism (Section 4.4). FACK detects
78 * that the actual data in the network (inflight) exceeds the target window
79 * (cwnd) due to the initial Slow-Start overshoot.
80 * - Consequently, FACK inhibits transmission (visible as a throughput dip) to
81 * allow the excess queue to drain.
82 */
83#include "ns3/applications-module.h"
84#include "ns3/core-module.h"
85#include "ns3/flow-monitor-module.h"
86#include "ns3/internet-module.h"
87#include "ns3/network-module.h"
88#include "ns3/point-to-point-module.h"
89#include "ns3/traffic-control-module.h"
90
91using namespace ns3;
92using namespace ns3::SystemPath;
93
94std::string g_dir; //!< Output directory
95std::ofstream g_throughput; //!< Throughput output stream
96std::ofstream g_queueSize; //!< Queue size output stream
97std::ofstream g_individualLostSegments; //!< Lost segments output stream
98
99uint32_t g_prev = 0; //!< Previous throughput byte count
100Time g_prevTime; //!< Previous throughput measurement time
101uint32_t g_segmentSize = 1448; //!< Segment size
102
103/**
104 * @brief Calculate and trace throughput
105 * @param monitor The flow monitor
106 */
107static void
109{
110 FlowMonitor::FlowStatsContainer stats = monitor->GetFlowStats();
111 if (!stats.empty())
112 {
113 auto itr = stats.begin();
114 Time curTime = Now();
115 // Convert (curTime - prevTime) to microseconds so that throughput is in bits per
116 // microsecond (which is equivalent to Mbps)
117
118 g_throughput << curTime.GetSeconds() << "s "
119 << 8 * (itr->second.txBytes - g_prev) /
120 ((curTime - g_prevTime).ToDouble(Time::US))
121 << " Mbps" << std::endl;
122 g_prevTime = curTime;
123 g_prev = itr->second.txBytes;
124 }
126}
127
128/**
129 * @brief Check and trace the queue size
130 * @param qd The queue disc
131 */
132void
134{
135 uint32_t qsize = qd->GetCurrentSize().GetValue();
137 g_queueSize << Simulator::Now().GetSeconds() << " " << qsize << std::endl;
138}
139
140/**
141 * @brief Trace packets dropped at Queue
142 * @param stream The output stream
143 * @param item The queue disc item dropped
144 */
145static void
147{
148 Ptr<const Packet> p = item->GetPacket();
149
150 TcpHeader tcpHeader;
151 p->PeekHeader(tcpHeader);
152 uint32_t seqNum = tcpHeader.GetSequenceNumber().GetValue();
153
154 // The plots in the paper use segment index, not the raw sequence number.
155 // We calculate it by dividing by the segment size.
156 double segmentIndex = static_cast<double>(seqNum) / g_segmentSize;
157
158 *stream->GetStream() << Simulator::Now().GetSeconds() << " " << segmentIndex << std::endl;
159}
160
161/**
162 * @brief Trace congestion window
163 * @param stream The output stream
164 * @param oldval Old value
165 * @param newval New value
166 */
167static void
169{
170 *stream->GetStream() << Simulator::Now().GetSeconds() << " " << newval / g_segmentSize
171 << std::endl;
172}
173
174/**
175 * @brief Trace BytesInFlight in segments
176 * @param stream The output stream
177 * @param oldval Old value
178 * @param newval New value
179 */
180static void
182{
183 uint32_t segs = newval / g_segmentSize;
184 *stream->GetStream() << Simulator::Now().GetSeconds() << " " << segs << std::endl;
185}
186
187/**
188 * @brief Trace FACK's inflight(AWND) in segments
189 * @param stream The output stream
190 * @param oldval Old value
191 * @param newval New value
192 */
193static void
195{
196 uint32_t segs = newval / g_segmentSize;
197 *stream->GetStream() << Simulator::Now().GetSeconds() << " " << segs << std::endl;
198}
199
200/**
201 * @brief Bind the Congestion Window trace source.
202 * @param nodeId The node ID.
203 * @param socketId The socket ID.
204 */
205void
206TraceCwnd(uint32_t nodeId, uint32_t socketId)
207{
208 AsciiTraceHelper ascii;
209 Ptr<OutputStreamWrapper> stream = ascii.CreateFileStream(g_dir + "/cwnd.dat");
210 Config::ConnectWithoutContext("/NodeList/" + std::to_string(nodeId) +
211 "/$ns3::TcpL4Protocol/SocketList/" +
212 std::to_string(socketId) + "/CongestionWindow",
213 MakeBoundCallback(&CwndTracer, stream));
214}
215
216/**
217 * @brief Bind the BytesInFlight trace source.
218 * @param nodeId The node ID.
219 * @param socketId The socket ID.
220 */
221void
223{
224 AsciiTraceHelper ascii;
225 Ptr<OutputStreamWrapper> stream = ascii.CreateFileStream(g_dir + "/bytesInFlight.dat");
226 Config::ConnectWithoutContext("/NodeList/" + std::to_string(nodeId) +
227 "/$ns3::TcpL4Protocol/SocketList/" +
228 std::to_string(socketId) + "/BytesInFlight",
230}
231
232/**
233 * @brief Bind the FackAwnd trace source.
234 * @param nodeId The node ID.
235 * @param socketId The socket ID.
236 */
237void
239{
240 AsciiTraceHelper ascii;
241 Ptr<OutputStreamWrapper> stream = ascii.CreateFileStream(g_dir + "/fackAwnd.dat");
242 Config::ConnectWithoutContext("/NodeList/" + std::to_string(nodeId) +
243 "/$ns3::TcpL4Protocol/SocketList/" +
244 std::to_string(socketId) + "/FackAwnd",
246}
247
248int
249main(int argc, char* argv[])
250{
251 // Naming the output directory using local system time
252 time_t rawtime;
253 struct tm* timeinfo;
254 char buffer[80];
255 time(&rawtime);
256 timeinfo = localtime(&rawtime);
257 strftime(buffer, sizeof(buffer), "%d-%m-%Y-%I-%M-%S", timeinfo);
258 std::string currentTime(buffer);
259
260 std::string tcpTypeId = "TcpLinuxReno";
261 std::string queueDisc = "FifoQueueDisc";
262 uint32_t delAckCount = 2;
263 bool bql = true;
264 bool enablePcap = false;
265 bool sack = true;
266 bool fack = true;
267 Time stopTime = Seconds(10);
268
269 queueDisc = std::string("ns3::") + queueDisc;
270
271 Config::SetDefault("ns3::TcpSocket::SndBufSize", UintegerValue(4194304));
272 Config::SetDefault("ns3::TcpSocket::RcvBufSize", UintegerValue(6291456));
273 Config::SetDefault("ns3::TcpSocket::InitialCwnd", UintegerValue(10));
274 Config::SetDefault("ns3::TcpSocket::DelAckCount", UintegerValue(delAckCount));
275 Config::SetDefault("ns3::TcpSocket::SegmentSize", UintegerValue(g_segmentSize));
276 Config::SetDefault("ns3::TcpSocket::InitialSlowStartThreshold",
278 Config::SetDefault("ns3::DropTailQueue<Packet>::MaxSize", QueueSizeValue(QueueSize("1p")));
279 Config::SetDefault(queueDisc + "::MaxSize", QueueSizeValue(QueueSize("10p")));
280 Config::SetDefault("ns3::TcpSocketBase::Sack", BooleanValue(sack));
281
282 CommandLine cmd(__FILE__);
283 cmd.AddValue("tcpTypeId", "Transport protocol to use: TcpLinuxReno", tcpTypeId);
284 cmd.AddValue("enablePcap", "Enable/Disable pcap file generation", enablePcap);
285 cmd.AddValue("stopTime",
286 "Stop time for applications / simulation time will be stopTime + 1",
287 stopTime);
288 cmd.AddValue("fack", "Enable/Disable FACK", fack);
289
290 cmd.Parse(argc, argv);
291
292 Config::SetDefault("ns3::TcpL4Protocol::SocketType", StringValue("ns3::" + tcpTypeId));
293 Config::SetDefault("ns3::TcpSocketBase::Fack", BooleanValue(fack));
294
295 // The maximum send buffer size is set to 4194304 bytes (4MB) and the
296 // maximum receive buffer size is set to 6291456 bytes (6MB) in the Linux
297 // kernel. The same buffer sizes are used as default in this example.
298
299 AsciiTraceHelper asciiTraceHelper;
300 Ptr<OutputStreamWrapper> streamWrapper;
301
302 NodeContainer sender;
303 NodeContainer receiver;
304 NodeContainer routers;
305 sender.Create(1);
306 receiver.Create(1);
307 routers.Create(2);
308
309 // Create the point-to-point link helpers
310 PointToPointHelper bottleneckLink;
311 bottleneckLink.SetDeviceAttribute("DataRate", StringValue("1.5Mbps"));
312 bottleneckLink.SetChannelAttribute("Delay", StringValue("5ms"));
313
314 PointToPointHelper senderToR1;
315 senderToR1.SetDeviceAttribute("DataRate", StringValue("10Mbps"));
316 senderToR1.SetChannelAttribute("Delay", StringValue("2ms"));
317
318 PointToPointHelper r2ToReceiver;
319 r2ToReceiver.SetDeviceAttribute("DataRate", StringValue("10Mbps"));
320 r2ToReceiver.SetChannelAttribute("Delay", StringValue("33ms"));
321
322 // Create NetDevice containers
323 NetDeviceContainer senderEdge = senderToR1.Install(sender.Get(0), routers.Get(0));
324 NetDeviceContainer r1r2 = bottleneckLink.Install(routers.Get(0), routers.Get(1));
325 NetDeviceContainer receiverEdge = r2ToReceiver.Install(routers.Get(1), receiver.Get(0));
326
327 // Install Stack
329 internet.Install(sender);
330 internet.Install(receiver);
331 internet.Install(routers);
332
333 // Configure the root queue discipline
335 tch.SetRootQueueDisc(queueDisc);
336
337 if (bql)
338 {
339 tch.SetQueueLimits("ns3::DynamicQueueLimits", "HoldTime", StringValue("1000ms"));
340 }
341
342 tch.Install(senderEdge);
343 tch.Install(receiverEdge);
344
345 // Assign IP addresses
347 ipv4.SetBase("10.0.0.0", "255.255.255.0");
348
349 Ipv4InterfaceContainer i1i2 = ipv4.Assign(r1r2);
350
351 ipv4.NewNetwork();
352 Ipv4InterfaceContainer is1 = ipv4.Assign(senderEdge);
353
354 ipv4.NewNetwork();
355 Ipv4InterfaceContainer ir1 = ipv4.Assign(receiverEdge);
356
357 // Populate routing tables
359
360 // Select sender side port
361 uint16_t port = 50001;
362
363 // Install application on the sender
364 BulkSendHelper source("ns3::TcpSocketFactory", InetSocketAddress(ir1.GetAddress(1), port));
365 source.SetAttribute("MaxBytes", UintegerValue(0));
366 ApplicationContainer sourceApps = source.Install(sender.Get(0));
367 sourceApps.Start(Seconds(0.1));
368 // Hook trace source after application starts
372 sourceApps.Stop(stopTime);
373
374 // Install application on the receiver
375 PacketSinkHelper sink("ns3::TcpSocketFactory", InetSocketAddress(Ipv4Address::GetAny(), port));
376 ApplicationContainer sinkApps = sink.Install(receiver.Get(0));
377 sinkApps.Start(Seconds(0));
378 sinkApps.Stop(stopTime);
379
380 // Create a new directory to store the output of the program
381 g_dir = "fack-results/" + currentTime + "/";
383
384 // Open the lostSegments.dat file and write packet-loss events into it.
385 g_individualLostSegments.open(g_dir + "/lostSegments.dat", std::ios::out);
386 Ptr<OutputStreamWrapper> streamWrapperIndivLost =
387 asciiTraceHelper.CreateFileStream(g_dir + "/lostSegments.dat");
388
389 // Trace the queue occupancy on the second interface of R1
390 tch.Uninstall(routers.Get(0)->GetDevice(1));
392 qd = tch.Install(routers.Get(0)->GetDevice(1));
393
394 // Get the actual QueueDisc Ptr and connect the "Drop" trace source
395 Ptr<QueueDisc> q = qd.Get(0);
396 q->TraceConnectWithoutContext("Drop",
397 MakeBoundCallback(&TracePacketDrop, streamWrapperIndivLost));
398
400 // Generate PCAP traces if it is enabled
401 if (enablePcap)
402 {
403 MakeDirectories(g_dir + "pcap/");
404 bottleneckLink.EnablePcapAll(g_dir + "/pcap/fack", true);
405 }
406
407 // Open files for writing throughput traces and queue size
408 g_throughput.open(g_dir + "/throughput.dat", std::ios::out);
409 g_queueSize.open(g_dir + "/queueSize.dat", std::ios::out);
410
411 NS_ASSERT_MSG(g_throughput.is_open(), "Throughput file was not opened correctly");
412 NS_ASSERT_MSG(g_queueSize.is_open(), "Queue size file was not opened correctly");
413
414 // Check for dropped packets using Flow Monitor
415 FlowMonitorHelper flowmon;
416 Ptr<FlowMonitor> monitor = flowmon.InstallAll();
417 Simulator::Schedule(Seconds(0 + 0.000001), &TraceThroughput, monitor);
418
422
423 g_throughput.close();
424 g_queueSize.close();
425
426 return 0;
427}
Ipv4InterfaceContainer i1i2
IPv4 interface container i1 + i2.
uint32_t q
holds a vector of ns3::Application pointers.
void Start(Time start) const
Start all of the Applications in this container at the start time given as a parameter.
void Stop(Time stop) const
Arrange for all of the Applications in this container to Stop() at the Time given as a parameter.
Manage ASCII trace files for device models.
Ptr< OutputStreamWrapper > CreateFileStream(std::string filename, std::ios::openmode filemode=std::ios::out)
Create and initialize an output stream object we'll use to write the traced bits.
AttributeValue implementation for Boolean.
Definition boolean.h:26
A helper to make it easier to instantiate an ns3::BulkSendApplication on a set of nodes.
Parse command-line arguments.
Helper to enable IP flow monitoring on a set of Nodes.
Ptr< FlowMonitor > InstallAll()
Enable flow monitoring on all nodes.
std::map< FlowId, FlowStats > FlowStatsContainer
Container: FlowId, FlowStats.
an Inet address class
aggregate IP/TCP/UDP functionality to existing Nodes.
A helper class to make life easier while doing simple IPv4 address assignment in scripts.
static Ipv4Address GetAny()
static void PopulateRoutingTables()
Build a routing database and initialize the routing tables of the nodes in the simulation.
holds a vector of std::pair of Ptr<Ipv4> and interface index.
Ipv4Address GetAddress(uint32_t i, uint32_t j=0) const
holds a vector of ns3::NetDevice pointers
keep track of a set of node pointers.
void Create(uint32_t n)
Create n nodes and append pointers to them to the end of this NodeContainer.
Ptr< Node > Get(uint32_t i) const
Get the Ptr<Node> stored in this container at a given index.
Ptr< NetDevice > GetDevice(uint32_t index) const
Retrieve the index-th NetDevice associated to this node.
Definition node.cc:138
A helper to make it easier to instantiate an ns3::PacketSinkApplication on a set of nodes.
void EnablePcapAll(std::string prefix, bool promiscuous=false)
Enable pcap output on each device (which is of the appropriate type) in the set of all nodes created ...
Build a set of PointToPointNetDevice objects.
void SetDeviceAttribute(std::string name, const AttributeValue &value)
Set an attribute value to be propagated to each NetDevice created by the helper.
void SetChannelAttribute(std::string name, const AttributeValue &value)
Set an attribute value to be propagated to each Channel created by the helper.
NetDeviceContainer Install(NodeContainer c)
Smart pointer class similar to boost::intrusive_ptr.
Definition ptr.h:70
Holds a vector of ns3::QueueDisc pointers.
Ptr< QueueDisc > Get(std::size_t i) const
Get the Ptr<QueueDisc> stored in this container at a given index.
Class for representing queue sizes.
Definition queue-size.h:85
AttributeValue implementation for QueueSize.
Definition queue-size.h:210
NUMERIC_TYPE GetValue() const
Extracts the numeric value of the sequence number.
static EventId Schedule(const Time &delay, FUNC f, Ts &&... args)
Schedule an event to expire after delay.
Definition simulator.h:580
static void Destroy()
Execute the events scheduled with ScheduleDestroy().
Definition simulator.cc:125
static Time Now()
Return the current simulation virtual time.
Definition simulator.cc:191
static void Run()
Run the simulation.
Definition simulator.cc:161
static EventId ScheduleNow(FUNC f, Ts &&... args)
Schedule an event to expire Now.
Definition simulator.h:614
static void Stop()
Tell the Simulator the calling event should be the last one executed.
Definition simulator.cc:169
Hold variables of type string.
Definition string.h:45
Header for the Transmission Control Protocol.
Definition tcp-header.h:36
SequenceNumber32 GetSequenceNumber() const
Get the sequence number.
Simulation virtual time values and global simulation resolution.
Definition nstime.h:96
double GetSeconds() const
Get an approximation of the time stored in this instance in the indicated unit.
Definition nstime.h:399
Time TimeStep(uint64_t ts)
Scheduler interface.
Definition nstime.h:1478
@ US
microsecond
Definition nstime.h:109
Build a set of QueueDisc objects.
QueueDiscContainer Install(NetDeviceContainer c)
uint16_t SetRootQueueDisc(const std::string &type, Args &&... args)
Helper function used to set a root queue disc of the given type and with the given attributes.
void SetQueueLimits(std::string type, Args &&... args)
Helper function used to add a queue limits object to the transmission queues of the devices.
void Uninstall(NetDeviceContainer c)
Hold an unsigned integer type.
Definition uinteger.h:34
uint16_t port
Definition dsdv-manet.cc:33
Time stopTime
Time g_prevTime
Previous throughput measurement time.
void TraceBytesInFlight(uint32_t nodeId, uint32_t socketId)
Bind the BytesInFlight trace source.
std::string g_dir
Output directory.
std::ofstream g_throughput
Throughput output stream.
static void BytesInFlightTracer(Ptr< OutputStreamWrapper > stream, uint32_t oldval, uint32_t newval)
Trace BytesInFlight in segments.
uint32_t g_segmentSize
Segment size.
void TraceFackAwnd(uint32_t nodeId, uint32_t socketId)
Bind the FackAwnd trace source.
std::ofstream g_individualLostSegments
Lost segments output stream.
static void FackAwndTracer(Ptr< OutputStreamWrapper > stream, uint32_t oldval, uint32_t newval)
Trace FACK's inflight(AWND) in segments.
static void CwndTracer(Ptr< OutputStreamWrapper > stream, uint32_t oldval, uint32_t newval)
Trace congestion window.
std::ofstream g_queueSize
Queue size output stream.
uint32_t g_prev
Previous throughput byte count.
void TraceCwnd(uint32_t nodeId, uint32_t socketId)
Bind the Congestion Window trace source.
static void TracePacketDrop(Ptr< OutputStreamWrapper > stream, Ptr< const QueueDiscItem > item)
Trace packets dropped at Queue.
static void TraceThroughput(Ptr< FlowMonitor > monitor)
Calculate and trace throughput.
void CheckQueueSize(Ptr< QueueDisc > qd)
Check and trace the queue size.
#define NS_ASSERT_MSG(condition, message)
At runtime, in debugging builds, if this condition is not true, the program prints the message to out...
Definition assert.h:75
void SetDefault(std::string name, const AttributeValue &value)
Definition config.cc:886
void ConnectWithoutContext(std::string path, const CallbackBase &cb)
Definition config.cc:946
auto MakeBoundCallback(R(*fnPtr)(Args...), BArgs &&... bargs)
Make Callbacks with varying number of bound arguments.
Definition callback.h:753
Time Now()
create an ns3::Time instance which contains the current simulation time.
Definition simulator.cc:288
void MakeDirectories(std::string path)
Create all the directories leading to path.
Time Seconds(double value)
Construct a Time in the indicated unit.
Definition nstime.h:1381
Time MilliSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition nstime.h:1398
Namespace for various file and directory path functions.
Every class exported by the ns3 library is enclosed in the ns3 namespace.
Ptr< PacketSink > sink
Pointer to the packet sink application.
Definition wifi-tcp.cc:44