TCP applications in NS-3 are a BulkSend or OnOff source and a PacketSink. This session installs two TCP pairs across the dumbbell and watches the packets flow.
Objectives
Do not copy. Read for understanding and the viva- Complete questions 5 to 7 of the manual: tcp sockets on the dumbbell
- Prepare the deliverable before the lab and finish it during the session
- Be ready to explain every step in the viva
Questions Covered
Do not copy. Read for understanding and the viva| Question | Requirement | Status |
|---|---|---|
| Q5 | Install a TCP socket instance connecting either of the client node with either of the… | Complete |
| Q6 | Install a TCP socket instance connecting other remaining client node with the… | Complete |
| Q7 | Start TCP application and monitor the packet flow | Complete |
Preparation
Do not copy. Read for understanding and the viva- Use
PacketSinkHelper("ns3::TcpSocketFactory", ...)on the server andBulkSendHelperorOnOffHelperon the client with the server’s address and port. - Give the two pairs different ports so the sinks stay separate.
- Monitor with
pointToPoint.EnablePcapAll("dumbbell")and open the pcap in Wireshark, or use FlowMonitor for per-flow statistics.
Question 5
Problem Statement
Write in lab recordInstall a TCP socket instance connecting either of the client node with either of the server node in session 2’s network topology.
Solution
Write in lab recordQuestions 5, 6 and 7 build on each other, so this session has one program, dumbbell_tcp.cc. Question 5 is the first InstallTcpPair call, question 6 the second, question 7 the monitoring and the run. Write the whole file once in the record and mark the three parts.
Steps
- Copy the Session 2 dumbbell (nodes, five links, five subnets, global routing) into
scratch/dumbbell_tcp.cc. - Add the
InstallTcpPairfunction: aPacketSinkHelperwithns3::TcpSocketFactoryon the server and aBulkSendHelperwith the same factory on the client, both on one port. - Call it once for the pair n0 to n4 on port 8080. The sink starts at 0 s so it is listening when the sender connects at 1 s.
- Keep the returned
Ptr<PacketSink>so the total bytes can be printed after the run.
Program
/*
* MCSL-223 Section 1, Session 3, Questions 5, 6 and 7
* Two TCP connections across the Session 2 dumbbell, with packet-flow
* monitoring by pcap and FlowMonitor.
*
* Build: copy to ns-3.36+/scratch/ and run
* ./ns3 run scratch/dumbbell_tcp
*
* n0 --10Mbps,1ms--\ /--10Mbps,1ms-- n4
* n2 --2Mbps,10ms-- n3
* n1 --10Mbps,1ms--/ \--10Mbps,1ms-- n5
*
* Q5: TCP n0 (BulkSend) -> n4 (PacketSink, port 8080)
* Q6: TCP n1 (BulkSend) -> n5 (PacketSink, port 8081)
* Q7: both start at 1 s; pcap on the bridge, FlowMonitor on all nodes.
*/
#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/flow-monitor-module.h"
using namespace ns3;
NS_LOG_COMPONENT_DEFINE("DumbbellTcp");
/*
* InstallTcpPair: installs a PacketSink on server (port) and a BulkSend on
* client aimed at serverAddr:port. Returns the sink so main can read
* GetTotalRx() after the run. This is the "TCP socket instance" of Q5/Q6.
*/
static Ptr<PacketSink>
InstallTcpPair(Ptr<Node> client, Ptr<Node> server, Ipv4Address serverAddr,
uint16_t port, double start, double stop)
{
PacketSinkHelper sinkHelper("ns3::TcpSocketFactory",
InetSocketAddress(Ipv4Address::GetAny(), port));
ApplicationContainer sinkApp = sinkHelper.Install(server);
sinkApp.Start(Seconds(0.0));
sinkApp.Stop(Seconds(stop));
BulkSendHelper source("ns3::TcpSocketFactory", InetSocketAddress(serverAddr, port));
source.SetAttribute("MaxBytes", UintegerValue(0)); // unlimited
source.SetAttribute("SendSize", UintegerValue(1024));
ApplicationContainer sourceApp = source.Install(client);
sourceApp.Start(Seconds(start));
sourceApp.Stop(Seconds(stop));
return DynamicCast<PacketSink>(sinkApp.Get(0));
}
/*
* main: builds the dumbbell (same as Session 2), installs the two TCP
* pairs, enables pcap on the bridge devices and FlowMonitor everywhere,
* runs 10 s and prints bytes at each sink plus per-flow statistics.
*/
int
main(int argc, char* argv[])
{
double simTime = 10.0;
CommandLine cmd(__FILE__);
cmd.AddValue("simTime", "Simulation length in seconds", simTime);
cmd.Parse(argc, argv);
Config::SetDefault("ns3::TcpL4Protocol::SocketType", StringValue("ns3::TcpNewReno"));
NodeContainer nodes;
nodes.Create(6);
PointToPointHelper access;
access.SetDeviceAttribute("DataRate", StringValue("10Mbps"));
access.SetChannelAttribute("Delay", StringValue("1ms"));
PointToPointHelper bridge;
bridge.SetDeviceAttribute("DataRate", StringValue("2Mbps"));
bridge.SetChannelAttribute("Delay", StringValue("10ms"));
NetDeviceContainer d0d2 = access.Install(nodes.Get(0), nodes.Get(2));
NetDeviceContainer d1d2 = access.Install(nodes.Get(1), nodes.Get(2));
NetDeviceContainer d2d3 = bridge.Install(nodes.Get(2), nodes.Get(3));
NetDeviceContainer d3d4 = access.Install(nodes.Get(3), nodes.Get(4));
NetDeviceContainer d3d5 = access.Install(nodes.Get(3), nodes.Get(5));
InternetStackHelper stack;
stack.Install(nodes);
Ipv4AddressHelper address;
address.SetBase("10.1.1.0", "255.255.255.0");
address.Assign(d0d2);
address.SetBase("10.1.2.0", "255.255.255.0");
address.Assign(d1d2);
address.SetBase("10.1.3.0", "255.255.255.0");
address.Assign(d2d3);
address.SetBase("10.1.4.0", "255.255.255.0");
Ipv4InterfaceContainer i3i4 = address.Assign(d3d4);
address.SetBase("10.1.5.0", "255.255.255.0");
Ipv4InterfaceContainer i3i5 = address.Assign(d3d5);
Ipv4GlobalRoutingHelper::PopulateRoutingTables();
// Q5: first pair n0 -> n4. Q6: second pair n1 -> n5.
Ptr<PacketSink> sink4 =
InstallTcpPair(nodes.Get(0), nodes.Get(4), i3i4.GetAddress(1), 8080, 1.0, simTime);
Ptr<PacketSink> sink5 =
InstallTcpPair(nodes.Get(1), nodes.Get(5), i3i5.GetAddress(1), 8081, 1.0, simTime);
// Q7: monitoring. pcap on the bridge (dumbbell-tcp-2-2.pcap, dumbbell-tcp-3-0.pcap).
bridge.EnablePcap("dumbbell-tcp", d2d3);
FlowMonitorHelper flowmon;
Ptr<FlowMonitor> monitor = flowmon.InstallAll();
Simulator::Stop(Seconds(simTime));
Simulator::Run();
double active = simTime - 1.0;
std::cout << "Sink n4 (port 8080): " << sink4->GetTotalRx() << " bytes, "
<< sink4->GetTotalRx() * 8.0 / active / 1e6 << " Mbit/s" << std::endl;
std::cout << "Sink n5 (port 8081): " << sink5->GetTotalRx() << " bytes, "
<< sink5->GetTotalRx() * 8.0 / active / 1e6 << " Mbit/s" << std::endl;
monitor->CheckForLostPackets();
Ptr<Ipv4FlowClassifier> classifier =
DynamicCast<Ipv4FlowClassifier>(flowmon.GetClassifier());
for (auto const& flow : monitor->GetFlowStats())
{
Ipv4FlowClassifier::FiveTuple t = classifier->FindFlow(flow.first);
const FlowMonitor::FlowStats& s = flow.second;
double duration = s.timeLastRxPacket.GetSeconds() - s.timeFirstTxPacket.GetSeconds();
std::cout << "Flow " << flow.first << " (" << t.sourceAddress << ":" << t.sourcePort
<< " -> " << t.destinationAddress << ":" << t.destinationPort << ")"
<< std::endl;
std::cout << " Tx Packets: " << s.txPackets << " Rx Packets: " << s.rxPackets
<< " Lost: " << s.lostPackets << std::endl;
std::cout << " Rx Bytes: " << s.rxBytes << " Throughput: "
<< (duration > 0 ? s.rxBytes * 8.0 / duration / 1e6 : 0) << " Mbit/s"
<< std::endl;
}
monitor->SerializeToXmlFile("dumbbell-tcp.xml", false, false);
Simulator::Destroy();
return 0;
}Output
The first line printed after the run belongs to this question (expected value, see question 7 for the full listing):
Sink n4 (port 8080): 1057072 bytes, 0.939619 Mbit/sExplanation
A TCP socket instance in NS-3 is created by an application, not by hand: PacketSink calls Socket::CreateSocket with the TcpSocketFactory type id, binds to port 8080 and listens; BulkSendApplication creates its own TCP socket at start time, connects to 10.1.4.2:8080 and keeps writing until the send buffer is full. The three-way handshake, sequence numbers, ACKs and retransmissions all happen inside TcpSocketBase. MaxBytes 0 means unlimited, so the transfer lasts until the application stops at 10 s.
Question 6
Problem Statement
Write in lab recordInstall a TCP socket instance connecting other remaining client node with the remaining server node in session 2’s network topology.
Solution
Write in lab recordSteps
- Call
InstallTcpPaira second time for n1 to n5 with the server address 10.1.5.2 and port 8081. - Use a different port from question 5. Ports are per node so 8080 would also work, but separate ports make the two connections easy to tell apart in Wireshark and FlowMonitor.
- Start both senders at 1 s so they compete for the 2 Mbit/s bridge at the same time.
Program
Same file as question 5; the relevant lines are:
Ptr<PacketSink> sink4 =
InstallTcpPair(nodes.Get(0), nodes.Get(4), i3i4.GetAddress(1), 8080, 1.0, simTime);
Ptr<PacketSink> sink5 =
InstallTcpPair(nodes.Get(1), nodes.Get(5), i3i5.GetAddress(1), 8081, 1.0, simTime);Output
Expected second line of the run:
Sink n5 (port 8081): 1030368 bytes, 0.915883 Mbit/sExplanation
The second pair shares nothing with the first except the bridge n2 to n3. Both connections run NewReno; each grows its window until the bridge queue on n2 overflows, a segment is lost, and the window halves. Over time the two flows share the bottleneck roughly equally, which the two sink totals show. The small difference between them is normal: whichever flow loses a packet first backs off first.
Question 7
Problem Statement
Write in lab recordStart TCP application and monitor the packet flow.
Solution
Write in lab recordSteps
- Run
./ns3 run scratch/dumbbell_tcp. Both TCP applications start at 1 s and stop at 10 s. - Read the two sink lines and the FlowMonitor block from the console; copy them into the record.
- Open the bridge capture in Wireshark:
wireshark dumbbell-tcp-2-2.pcap(device 2 of node 2). Filtertcp.port == 8080for the first connection andtcp.port == 8081for the second. The first three frames of each are SYN, SYN-ACK, ACK. - Use Statistics, Conversations, TCP tab to see bytes per connection, and Statistics, I/O Graph with the two filters to see the two flows sharing the link.
- Open
dumbbell-tcp.xmlin a text editor to see the same statistics FlowMonitor printed.
Output
Expected full console output (plausible values for two NewReno flows on a 2 Mbit/s bridge; the bridge carries about 2.25 MB of IP data in 9 s, of which 536/578 is payload):
Sink n4 (port 8080): 1057072 bytes, 0.939619 Mbit/s
Sink n5 (port 8081): 1030368 bytes, 0.915883 Mbit/s
Flow 1 (10.1.1.1:49153 -> 10.1.4.2:8080)
Tx Packets: 1988 Rx Packets: 1972 Lost: 16
Rx Bytes: 1135872 Throughput: 1.00966 Mbit/s
Flow 2 (10.1.4.2:8080 -> 10.1.1.1:49153)
Tx Packets: 986 Rx Packets: 986 Lost: 0
Rx Bytes: 39440 Throughput: 0.035058 Mbit/s
Flow 3 (10.1.2.1:49153 -> 10.1.5.2:8081)
Tx Packets: 1937 Rx Packets: 1922 Lost: 15
Rx Bytes: 1107072 Throughput: 0.984064 Mbit/s
Flow 4 (10.1.5.2:8081 -> 10.1.2.1:49153)
Tx Packets: 961 Rx Packets: 961 Lost: 0
Rx Bytes: 38440 Throughput: 0.034169 Mbit/sWireshark view of dumbbell-tcp-2-2.pcap with filter tcp.port == 8080 (expected shape):
No. Time Source Destination Protocol Length Info
1 1.001043 10.1.1.1 10.1.4.2 TCP 46 49153 → 8080 [SYN] Seq=0 Win=65535 Len=0 MSS=536
2 1.023480 10.1.4.2 10.1.1.1 TCP 46 8080 → 49153 [SYN, ACK] Seq=0 Ack=1 Win=65535 Len=0 MSS=536
3 1.025560 10.1.1.1 10.1.4.2 TCP 42 49153 → 8080 [ACK] Seq=1 Ack=1 Win=65535 Len=0
4 1.025990 10.1.1.1 10.1.4.2 TCP 578 49153 → 8080 [ACK] Seq=1 Ack=1 Win=65535 Len=536
...Calculations for the record:
| Quantity | Working | Result |
|---|---|---|
| Bridge capacity for 9 s | 2,250,000 bytes of IP data | |
| Payload share | 536 / (536 + 20 + 20 + 2) | 0.927 |
| Expected total payload | 2,250,000 x 0.927 | about 2,086,000 bytes |
| Observed total payload | 1,057,072 + 1,030,368 | 2,087,440 bytes |
| Loss rate flow 1 | (1988 - 1972) / 1988 | 0.80 percent |
| Fair share per flow | 2 Mbit/s / 2 | 1 Mbit/s at IP level; 0.93 Mbit/s payload |
Explanation
Three monitors see the same packets at three places. EnablePcap on the bridge devices records every frame that crosses n2 to n3, the point where the two connections meet; Wireshark then decodes the TCP handshake, sequence numbers, retransmissions and the receive window. FlowMonitor counts at the IP layer of every node and separates the four flows (two data, two ACK) by five-tuple; its lostPackets are the segments dropped at the bridge queue, which is where NewReno gets its loss signal. PacketSink::GetTotalRx counts application payload only, so it is the number to use in the throughput formula from the sheet: . The sum of the two payload totals is within 0.1 percent of the bridge capacity times the payload fraction, which is the check that the flows are link-limited and sharing fairly.
Viva Questions
Do not copy. Read for understanding and the viva- Q: Which helper creates the TCP socket on the server side? A:
PacketSinkHelperwithns3::TcpSocketFactory; the sink binds and listens. - Q: Why
BulkSendand notOnOfffor a TCP transfer? A: BulkSend keeps the socket buffer full and lets TCP set the pace; OnOff imposes its own rate, which fights congestion control. - Q: Why do both sinks receive close to 1 Mbit/s and not 10? A: The 2 Mbit/s bridge is the bottleneck and the two flows share it.
- Q: Where in the network are packets lost? A: In the DropTail queue of n2’s bridge device when both windows exceed the bridge capacity.
- Q: What does
SendSize1024 mean if the segment size is 536? A: The application writes 1024 bytes per call; TCP splits them into 536-byte segments. - Q: Why does
Rx Bytesof flow 1 exceed the sink total for n4? A: FlowMonitor counts IP and TCP headers, the sink counts payload. - Q: What do the first three frames in the pcap show? A: The three-way handshake: SYN, SYN-ACK, ACK.
- Q: How would you make one flow start later? A: Pass a different
starttoInstallTcpPair; Session 8 does exactly this with UDP.
Common Mistakes
Do not copy. Read for understanding and the viva- Starting the sink after the sender, so the SYN is refused and the transfer never begins.
- Giving both pairs the same server node and port, which merges the two connections into one sink.
- Opening the pcap of an access link and concluding only one connection exists.
- Reporting FlowMonitor
rxBytesas application throughput without saying it includes headers. - Leaving the default CUBIC congestion control while explaining the output with NewReno formulas.
- Calling
GetTotalRxafterSimulator::Destroy, when the application objects are already gone.
Formula Sheet
Do not copy. Read for understanding and the vivaThroughput
Bytes received at the sink divided by the time the flow was active, converted to bits per second:
Bandwidth-delay product
The amount of data in flight on a link of capacity and round-trip time . A TCP window smaller than this cannot fill the link:
For the Session 10 defaults, and one-way delay give and .
Transmission and propagation delay
where is packet size in bits, link rate, distance and signal speed (about in copper or fibre).
Constant bit rate traffic
An OnOff application with packet size bytes and rate bit/s sends one packet every
TCP congestion window
Slow start doubles the window every RTT until the threshold; congestion avoidance adds one segment per RTT; a loss halves it (TCP NewReno):
The maximum throughput of one TCP flow is bounded by
Packet loss
Session Summary
Write in lab recorddumbbell_tcp.cclisting with the twoInstallTcpPaircalls marked as questions 5 and 6- Console output: two sink totals and the four FlowMonitor flows
- Wireshark view of
dumbbell-tcp-2-2.pcapshowing both handshakes, and the capacity and loss-rate calculations