A Single TCP Sender| The ns-3 Network Simulator

A Single TCP Sender

We begin by translating the single-TCP-sender script of 31.2 A Single TCP Sender. The full program is in basic1.cc; we now review most of it line-by-line; some standard things such as #include directives are omitted.

 Network topology:


 A--R: 10 Mbps / 10 ms delay
 R--B: 800 kbps / 50 ms delay
 queue at R: size 7

 using namespace ns3;

 std::string fileNameRoot = "basic1"; // base name for   trace files, etc


An Introduction to Computer Networks, Release 2.0.4

void CwndChange (Ptr stream, uint32_t oldCwnd, uint32_t ãÑnewCwnd)


*stream->GetStream () << Simulator::Now ().GetSeconds () << " " << newCwnd ãÑ<< std::endl;


static void TraceCwnd () // Trace changes to the congestion window


AsciiTraceHelper ascii; Ptr stream = ascii.CreateFileStream (fileNameRoot + ". ãÑcwnd");

Config::ConnectWithoutContext ("/NodeList/0/$ns3::TcpL4Protocol/SocketList/ ãÑ0/CongestionWindow", MakeBoundCallback (&CwndChange,stream));


The function TraceCwnd() arranges for tracing of cwnd; the function CwndChange is a callback, invoked by the ns-3 system whenever cwnd changes. Such callbacks are common in ns-3.

The parameter string beginning /NodeList/0/... is an example of the configuration namespace. Each ns-3 attribute can be accessed this way. See 32.2.2 The Ascii Tracefile below.

int main (int argc, char *argv[])

int tcpSegmentSize = 1000;
Config::SetDefault ("ns3::TcpSocket::SegmentSize", UintegerValue
Config::SetDefault ("ns3::TcpSocket::DelAckCount", UintegerValue (0)); Config::SetDefault ("ns3::TcpL4Protocol::SocketType", StringValue ( ãÑ"ns3::TcpReno")); Config::SetDefault ("ns3::RttEstimator::MinRTO", TimeValue (MilliSeconds ãÑ(500)));


The use of Config::SetDefault() allows us to configure objects that will not exist until some later point, perhaps not until the ns-3 simulator is running. The first parameter is an attribute string, of the form ns3::class::attribute. A partial list of attributes is at https://www.nsnam.org/docs/release/3.19/ doxygen/group___attribute_list.html. Attributes of a class can also be determined by a command such as the following:

  ./waf --run "basic1 --PrintAttributes=ns3::TcpSocket

The advantage of the Config::SetDefault mechanism is that often objects are created indirectly, perhaps by “helper” classes, and so direct setting of class properties can be problematic.

It is perfectly acceptable to issue some Config::SetDefault calls, then create some objects (perhaps implicitly), and then change the defaults (again with Config::SetDefault) for creation of additional objects.

We pick the TCP congesion-control algorithm by setting ns3::TcpL4Protocol::SocketType. Options are TcpRfc793 (no congestion control), TcpTahoe, TcpReno, TcpNewReno and TcpWestwood. TCP Cubic and SACK TCP are not supported natively (though they are available if the Network Simulation Cradle is installed).

An Introduction to Computer Networks, Release 2.0.4

Setting the DelAckCount attribute to 0 disables delayed ACKs. Setting the MinRTO value to 500 ms avoids some unexpected hard timeouts. We will return to both of these below in 32.2.3 Unexpected Timeouts and Other Phenomena.

Next comes our local variables and command-line-option processing. In ns-3 the latter is handled via the CommandLine object, which also recognized the --PrintAttributes option above. Using the --PrintHelp option gives a list of variables that can be set via command-line arguments.

unsigned int runtime = 20; // seconds
int delayAR = 10; // ms
int delayRB = 50; // ms
double bottleneckBW= 0.8; // Mbps
double fastBW = 10; // Mbps
uint32_t queuesize = 7;
uint32_t maxBytes = 0; // 0 means "unlimited"
CommandLine cmd;
// Here, we define command line options overriding some of the above. cmd.AddValue ("runtime", "How long the applications should send data", ãÑruntime);
cmd.AddValue ("delayRB", "Delay on the R--B link, in ms", delayRB); cmd.AddValue ("queuesize", "queue size at R", queuesize);
cmd.AddValue ("tcpSegmentSize", "TCP segment size", tcpSegmentSize);

cmd.Parse (argc, argv);

std::cout << "queuesize=" << queuesize << ", delayRB=" << delayRB << ãÑstd::endl;


Next we create three nodes, illustrating the use of smart pointers and CreateObject().

Ptr A = CreateObject ();
Ptr R = CreateObject ();
Ptr B = CreateObject ();


Class Ptr is a “smart pointer” that manages memory through reference counting. The template function CreateObject acts as the ns-3 preferred alternative to operator new. Parameters for objects created this way can be supplied via Config::SetDefault, or by some later method call applied to the Ptr object. For Node objects, for example, we might call A -> AddDevice(...).

A convenient alternative to creating nodes individually is to create a container of nodes all at once:

NodeContainer allNodes;
Ptr A = allNodes.Get(0);


After the nodes are in place we create our point-to-point links, using the PointToPointHelper class. We also create NetDeviceContainer objects; we don’t use these here (we could simply call AR. Install(A,R)), but will need them below when assigning IPv4 addresses.

// use PointToPointChannel and PointToPointNetDevice
NetDeviceContainer devAR, devRB;


An Introduction to Computer Networks, Release 2.0.4

PointToPointHelper AR, RB;
// create point-to-point link from A to R
AR.SetDeviceAttribute ("DataRate", DataRateValue (DataRate (fastBW * 1000 * ãÑ1000)));
AR.SetChannelAttribute ("Delay", TimeValue (MilliSeconds (delayAR)));
devAR = AR.Install(A, R);

// create point-to-point link from R to B
RB.SetDeviceAttribute ("DataRate", DataRateValue (DataRate (bottleneckBW * ãÑ1000 * 1000)));
RB.SetChannelAttribute ("Delay", TimeValue (MilliSeconds (delayRB))); RB.SetQueue("ns3::DropTailQueue", "MaxPackets", UintegerValue(queuesize)); devRB = RB.Install(R,B);


Next we hand out IPv4 addresses. The Ipv4AddressHelper class can help us with individual LANs (eg A–R and R–B), but it is up to us to make sure our two LANs are on different subnets. If we attempt to put A and B on the same subnet, routing will simply fail, just as it would if we were to do this with real network nodes.

InternetStackHelper internet;
internet.Install (A);
internet.Install (R);
internet.Install (B);

// Assign IP addresses

Ipv4AddressHelper ipv4;
ipv4.SetBase ("", "");
Ipv4InterfaceContainer ipv4Interfaces;
ipv4Interfaces.Add (ipv4.Assign (devAR));
ipv4.SetBase ("", "");
ipv4Interfaces.Add (ipv4.Assign(devRB));

Ipv4GlobalRoutingHelper::PopulateRoutingTables ();


Next we print out the addresses assigned. This gives us a peek at the GetObject template and the ns-3 object-aggregation model. The original Node objects we created earlier were quite generic; they gained their Ipv4 component in the code above. Now we retrieve that component with the GetObject() calls below.

Ptr A4 = A->GetObject();// gets node A's IPv4 subsystem
Ptr B4 = B->GetObject();
Ptr R4 = R->GetObject();
Ipv4Address Aaddr = A4->GetAddress(1,0).GetLocal();
Ipv4Address Baddr = B4->GetAddress(1,0).GetLocal();
Ipv4Address Raddr = R4->GetAddress(1,0).GetLocal();
std::cout GetAddress(2,0).GetLocal() <<


In general, A->GetObject returns the component of type T that has been “aggregated” to Ptr A; often this aggregation is invisible to the script programmer but an understanding of how it works is sometimes useful. The aggregation is handled by the ns-3 Object class, which contains an internal list m_aggregates of aggregated companion objects. At most one object of a given type can be aggregated to another, making GetObject unambiguous. Given a Ptr A, we can obtain an iterator over the aggregated companions via A->GetAggregateIterator(), of type Object::AggregateIterator. From each Ptr B returned by this iterator, we can call B->GetInstanceTypeId().GetName() to get the class name of B.

The GetAddress() calls take two parameters; the first specfies the interface (a value of 0 gives the loopback interface) and the second distinguishes between multiple addresses assigned to the same interface (which is not happening here). The call A4->GetAddress(1,0) returns an Ipv4InterfaceAddress object containing, among other things, an IP address, a broadcast address and a netmask; GetLocal() returns the first of these.

Next we create the receiver on B, using a PacketSinkHelper. A receiver is, in essense, a read-only form of an application server.

// create a sink on B
uint16_t Bport = 80;
Address sinkAaddr(InetSocketAddress (Ipv4Address::GetAny (), Bport)); PacketSinkHelper sinkA ("ns3::TcpSocketFactory", sinkAaddr); ApplicationContainer sinkAppA = sinkA.Install (B);
sinkAppA.Start (Seconds (0.01));
// the following means the receiver will run 1 min longer than the sender app. sinkAppA.Stop (Seconds (runtime + 60.0));

Address sinkAddr(InetSocketAddress(Baddr, Bport));


Now comes the sending application, on A. We must configure and create a BulkSendApplication, attach it to A, and arrange for a connection to be created to B. The BulkSendHelper class simplifies this.

BulkSendHelper sourceAhelper ("ns3::TcpSocketFactory", sinkAddr);
sourceAhelper.SetAttribute ("MaxBytes", UintegerValue (maxBytes));
sourceAhelper.SetAttribute ("SendSize", UintegerValue (tcpSegmentSize));
ApplicationContainer sourceAppsA = sourceAhelper.Install (A);
sourceAppsA.Start (Seconds (0.0));
sourceAppsA.Stop (Seconds (runtime));


If we did not want to use the helper class here, the easiest way to create the BulkSendApplication is with an ObjectFactory. We configure the factory with the type we want to create and the relevant configuration parameters, and then call factory.Create(). (We could have used the Config::SetDefault() mechanism and CreateObject() as well.)

ObjectFactory factory;
factory.SetTypeId ("ns3::BulkSendApplication");
factory.Set ("Protocol", StringValue ("ns3::TcpSocketFactory"));
factory.Set ("MaxBytes", UintegerValue (maxBytes));
factory.Set ("SendSize", UintegerValue (tcpSegmentSize));
factory.Set ("Remote", AddressValue (sinkAddr));


An Introduction to Computer Networks, Release 2.0.4

Ptr bulkSendAppObj = factory.Create();
Ptr bulkSendApp = bulkSendAppObj -> GetObject();


The above gives us no direct access to the actual TCP connection. Yet another alternative is to start by creating the TCP socket and connecting it:

Ptr tcpsock = Socket::CreateSocket (A, TcpSocketFactory::GetTypeId ãÑ()); tcpsock->Bind();


However, there is then no mechanism for creating a BulkSendApplication that uses a pre-existing socket. (For a workaround, see the tutorial example fifth.cc.) Before beginning execution, we set up tracing; we will look at the tracefile format later. We use the AR PointToPointHelper class here, but both ascii and pcap tracing apply to the entire A–R–B network.

// Set up tracing
AsciiTraceHelper ascii;
std::string tfname = fileNameRoot + ".tr";
AR.EnableAsciiAll (ascii.CreateFileStream (tfname));
// Setup tracing for cwnd
Simulator::Schedule(Seconds(0.01),&TraceCwnd); // this Time cannot be 0. ãÑ0
// This tells ns-3 to generate pcap traces, including "-node#-dev#-" in ãÑfilename
AR.EnablePcapAll (fileNameRoot); // ".pcap" suffix is added automatically


                 This last creates four .pcap files, eg


The first number refers to the node (A=0, R=1, B=2) and the second to the interface. A packet arriving at R but dropped there will appear in the second .pcap file but not the third. These files can be viewed with WireShark.

Finally we are ready to start the simulator! The BulkSendApplication will stop at time runtime, but traffic may be in progress. We allow it an additional 60 seconds to clear. We also, after the simulation has run, print out the number of bytes received by B.

Simulator::Stop (Seconds (runtime+60));
Simulator::Run ();
Ptr sink1 = DynamicCast (sinkAppA.Get (0));
std::cout GetTotalRx () << ãÑstd::endl;


An Introduction to Computer Networks, Release 2.0.4

return 0;


Running the Script

When we run the script and plot the cwnd trace data (here for about 12 seconds), we get the following:
















Compare this graph to that in 31.2.1 Graph of cwnd v time produced by ns-2. The slow-start phase earlier ended at around 2.0 and now ends closer to 3.0. There are several modest differences, including the halving of cwnd just before T=1 and the peak around T=2.6; these were not apparent in the ns-2 graph.

After slow-start is over, the graphs are quite similar; cwnd ranges from 10 to 21. The period before was 1.946 seconds; here it is 2.0548; the difference is likely due to a more accurate implementation of the recovery algorithm.

One striking difference is the presence of the near-vertical line of dots just after each peak. What is happening here is that ns-3 implements the cwnd inflation/deflation algorithm outlined at the tail end of 19.4 TCP Reno and Fast Recovery. When three dupACKs are received, cwnd is set to cwnd/2 + 3, and is then allowed to increase to 1.5ˆcwnd. See the end of 19.4 TCP Reno and Fast Recovery.

The Ascii Tracefile

d 4.9823 /NodeList/1/DeviceList/1/$ns3::PointToPointNetDevice/TxQueue/Drop ãÑns3::PppHeader (Point-to-Point Protocol: IP (0x0021)) ns3::Ipv4Header (tos ãÑ0x0 DSCP Default ECN Not-ECT ttl 63 id 296 protocol 6 offset (bytes) 0 ãÑflags [none] length: 1040 > ns3::TcpHeader (49153 > 80 [ ãÑACK ] Seq=271001 Ack=1 Win=65535) Payload (size=1000) r 4.98312 /NodeList/2/DeviceList/0/$ns3::PointToPointNetDevice/MacRx ãÑns3::Ipv4Header (tos 0x0 DSCP Default ECN Not-ECT ttl 63 id 283 protocol 6 ãÑoffset (bytes) 0 flags [none] length: 1040 > ãÑns3::TcpHeader (49153 > 80 [ ACK ] Seq=258001 Ack=1 Win=65535) Payload


An Introduction to Computer Networks, Release 2.0.4

+ 4.98312 /NodeList/2/DeviceList/0/$ns3::PointToPointNetDevice/TxQueue/ ãÑEnqueue ns3::PppHeader (Point-to-Point Protocol: IP (0x0021)) ãÑns3::Ipv4Header (tos 0x0 DSCP Default ECN Not-ECT ttl 64 id 271 protocol 6 ãÑoffset (bytes) 0 flags [none] length: 40 > ãÑns3::TcpHeader (80 > 49153 [ ACK ] Seq=1 Ack=259001 Win=65535) - 4.98312 /NodeList/2/DeviceList/0/$ns3::PointToPointNetDevice/TxQueue/ ãÑDequeue ns3::PppHeader (Point-to-Point Protocol: IP (0x0021)) ãÑns3::Ipv4Header (tos 0x0 DSCP Default ECN Not-ECT ttl 64 id 271 protocol 6 ãÑoffset (bytes) 0 flags [none] length: 40 > ãÑns3::TcpHeader (80 > 49153 [ ACK ] Seq=1 Ack=259001 Win=65535)


As with ns-2, the first letter indicates the action: r for received, d for dropped, + for enqueued, - for dequeued. For Wi-Fi tracefiles, t is for transmitted. The second field represents the time.

The third field represents the name of the event in the configuration namespace, sometimes called the configuration path name. The NodeList value represents the node (A=0, etc), the DeviceList represents the interface, and the final part of the name repeats the action: Drop, MacRx, Enqueue, Dequeue.

After that come a series of class names (eg ns3::Ipv4Header, ns3::TcpHeader), from the ns-3 attribute system, followed in each case by a parenthesized list of class-specific trace information.

In the output above, the final three records all refer to node B (/NodeList/2/). Packet 258 has just arrived (Seq=258001), and ACK 259001 is then enqueued and sent.

Unexpected Timeouts and Other Phenomena

In the discussion of the script above at 32.2 A Single TCP Sender we mentioned that we set ns3::TcpSocket::DelAckCount to 0, to disable delayed ACKs, and ns3::RttEstimator::MinRTO to 500 ms, to avoid unexpected timeouts.

If we comment out the line disabling delayed ACKs, little changes in our graph, except that the spacing between consecutive TCP teeth now almost doubles to 3.776. This is because with delayed ACKs the receiver sends only half as many ACKs, and the sender does not take this into account when incrementing cwnd (that is, the sender does not implement the suggestion of RFC 3465 mentioned in 19.2.1 TCP Reno Per-ACK Responses).

If we leave out the MinRTO adjustment, and set tcpSegmentSize to 960, we get a more serious problem: the graph now looks something like this:


An Introduction to Computer Networks, Release 2.0.4













We can enable ns-3’s internal logging in the TcpReno class by entering the commands below, before running the script. (In some cases, as with WifiHelper::EnableLogComponents(), logging output can be enabled from within the script.) Once enabled, logging output is written to stderr.

export NS_LOG


The log output shows the initial dupACK at 8.54:

8.54069 [node 0] Triple dupack. Reset cwnd to 12960, ssthresh to 10080


But then, despite Fast Recovery proceding normally, we get a hard timeout:

8.71463 [node 0] RTO. Reset cwnd to 960, ssthresh to 14400, restart from ãÑseqnum 510721


What is happening here is that the RTO interval was just a little too short, probably due to the use of the “awkward” segment size of 960.
After the timeout, there is another triple-dupACK!

8.90344 [node 0] Triple dupack. Reset cwnd to 6240, ssthresh to 3360


Shortly thereafter, at T=8.98, cwnd is reset to 3360, in accordance with the Fast Recovery rules.

The overall effect is that cwnd is reset, not to 10, but to about 3.4 (in packets). This significantly slows down throughput.

In recovering from the hard timeout, the sequence number is reset to Seq=510721 (packet 532), as this was the last packet acknowledged. Unfortunately, several later packets had in fact made it through to B. By looking at the tracefile, we can see that at T=8.7818, B received Seq=538561, or packet 561. Thus, when A begins retransmitting packets 533, 534, etc after the timeout, B’s response is to send the ACK the highest packet it has received, packet 561 (Ack=539521).

An Introduction to Computer Networks, Release 2.0.4

This scenario is not what the designers of Fast Recovery had in mind; it is likely triggered by a tooconservative timeout estimate. Still, exactly how to fix it is an interesting question; one approach might be to ignore, in Fast Recovery, triple dupACKs of packets now beyond what the sender is currently sending.
























































































































































































Frequently Asked Questions

Ans: Installing and Running ns-3|THE NS-3 NETWORK SIMULATOR view more..
Ans: It is now time to turn our attention from the applications and social aspects of networking (the fun stuff) to the technical issues involved in network design (the work stuff). There is no generally accepted taxonomy into which all computer networks fit, but two dimensions stand out as important: transmission technology and scale. view more..
Ans: Before we start to examine the technical issues in detail, it is worth devoting some time to pointing out why people are interested in computer networks and what they can be used for. After all, if nobody were interested in computer networks, few of them would be built. We will start with traditional uses at companies view more..
Ans: A Single TCP Sender| The ns-3 Network Simulator view more..
Ans: Wireless|The ns-3 Network Simulator view more..
Ans: The ns-2 simulator|NETWORK SIMULATIONS: NS-2 view more..
Ans: A Single TCP Sender| The ns-3 Network Simulator view more..
Ans: Two TCP Senders Competing|THE NS-3 NETWORK SIMULATOR view more..
Ans: Wireless Simulation|NETWORK SIMULATIONS: NS-2 view more..
Ans: Epilog|NETWORK SIMULATIONS: NS-2 view more..
Ans: Installing and Running ns-3|THE NS-3 NETWORK SIMULATOR view more..
Ans: Installing Mininet|MININET view more..
Ans: A Simple Mininet Example|MININET view more..
Ans: Multiple Switches in a Line|Mininet view more..
Ans: IP Routers in a Line|Mininet view more..
Ans: IP Routers With Simple Distance-Vector Implementation|Mininet view more..
Ans: TCP Competition: Reno vs BBR|Mininet view more..
Ans: Linux Traffic Control (tc)|Mininet view more..

Rating - 3/5