This session measures how end-to-end throughput changes as link delay grows, then builds the dumbbell topology (two clients, a bottleneck link, two servers) used by Sessions 3, 8 and 9.
Objectives
Do not copy. Read for understanding and the viva- Complete questions 3 to 4 of the manual: throughput versus latency and the dumbbell topology
- 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 |
|---|---|---|
| Q3 | Measure the throughput (end to end) while varying latency in the network created in… | Complete |
| Q4 | Create a simple network topology having two client node on left side and two server… | Complete |
Preparation
Do not copy. Read for understanding and the viva- Vary the point-to-point Delay attribute (1 ms, 10 ms, 50 ms, 100 ms) and read bytes received from the sink or FlowMonitor; tabulate throughput against delay.
- Dumbbell: nodes n0, n1 (clients), n2 and n3 (routers), n4, n5 (servers); five point-to-point links; give each link its own subnet.
- Enable global routing with
Ipv4GlobalRoutingHelper::PopulateRoutingTables()so packets cross the bridge.
Question 3
Problem Statement
Write in lab recordMeasure the throughput (end to end) while varying latency in the network created in Session 1.
Solution
Write in lab recordSteps
- Save the program as
scratch/throughput_vs_latency.cc. It is the Session 1 link with a TCPBulkSendApplicationon n0, aPacketSinkon n1 andFlowMonitoron both nodes. The delay comes from the command line. - Run it four times, once per latency:
./ns3 run "scratch/throughput_vs_latency --delay=1ms"
./ns3 run "scratch/throughput_vs_latency --delay=10ms"
./ns3 run "scratch/throughput_vs_latency --delay=50ms"
./ns3 run "scratch/throughput_vs_latency --delay=100ms"- From each run copy
Rx BytesandThroughputof flow 1 (the data flow 10.1.1.1 to 10.1.1.2; flow 2 is the ACK stream) into the table. - Each run also writes a FlowMonitor XML file (
throughput-1ms.xml,throughput-10ms.xmland so on); keep them as evidence. - Plot throughput against delay by hand or with gnuplot (
plot "table.dat" using 1:2 with linespoints).
Program
/*
* MCSL-223 Section 1, Session 2, Question 3
* End-to-end TCP throughput on the Session 1 link while the delay varies.
*
* Build: copy to ns-3.36+/scratch/ and run once per delay:
* ./ns3 run "scratch/throughput_vs_latency --delay=1ms"
* ./ns3 run "scratch/throughput_vs_latency --delay=10ms"
* ./ns3 run "scratch/throughput_vs_latency --delay=50ms"
* ./ns3 run "scratch/throughput_vs_latency --delay=100ms"
*
* n0 (BulkSend) ---------- n1 (PacketSink, port 5000)
* 10.1.1.1 5 Mbps, delay 10.1.1.2
*/
#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("ThroughputVsLatency");
/*
* main: builds the two-node link with the delay given on the command line,
* runs a TCP bulk transfer from n0 to n1 for simTime seconds and prints
* FlowMonitor statistics plus throughput = 8 * rxBytes / (tLastRx - tFirstTx).
*/
int
main(int argc, char* argv[])
{
std::string delay = "10ms";
double simTime = 10.0;
CommandLine cmd(__FILE__);
cmd.AddValue("delay", "One-way link delay, e.g. 1ms, 10ms, 50ms, 100ms", delay);
cmd.AddValue("simTime", "Simulation length in seconds", simTime);
cmd.Parse(argc, argv);
// NewReno so the formula sheet describes what the simulator does.
Config::SetDefault("ns3::TcpL4Protocol::SocketType", StringValue("ns3::TcpNewReno"));
NodeContainer nodes;
nodes.Create(2);
PointToPointHelper p2p;
p2p.SetDeviceAttribute("DataRate", StringValue("5Mbps"));
p2p.SetChannelAttribute("Delay", StringValue(delay));
NetDeviceContainer devices = p2p.Install(nodes);
InternetStackHelper stack;
stack.Install(nodes);
Ipv4AddressHelper address;
address.SetBase("10.1.1.0", "255.255.255.0");
Ipv4InterfaceContainer interfaces = address.Assign(devices);
uint16_t port = 5000;
// Sink on n1 accepts the TCP connection and counts bytes.
PacketSinkHelper sink("ns3::TcpSocketFactory",
InetSocketAddress(Ipv4Address::GetAny(), port));
ApplicationContainer sinkApp = sink.Install(nodes.Get(1));
sinkApp.Start(Seconds(0.0));
sinkApp.Stop(Seconds(simTime));
// BulkSend on n0 sends as fast as TCP allows (MaxBytes 0 = unlimited).
BulkSendHelper source("ns3::TcpSocketFactory",
InetSocketAddress(interfaces.GetAddress(1), port));
source.SetAttribute("MaxBytes", UintegerValue(0));
ApplicationContainer sourceApp = source.Install(nodes.Get(0));
sourceApp.Start(Seconds(1.0));
sourceApp.Stop(Seconds(simTime));
FlowMonitorHelper flowmon;
Ptr<FlowMonitor> monitor = flowmon.InstallAll();
Simulator::Stop(Seconds(simTime));
Simulator::Run();
// Per-flow statistics. Flow 1 is data n0->n1, flow 2 is the ACK stream.
monitor->CheckForLostPackets();
Ptr<Ipv4FlowClassifier> classifier =
DynamicCast<Ipv4FlowClassifier>(flowmon.GetClassifier());
std::cout << "Delay = " << delay << std::endl;
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();
double throughput = duration > 0 ? s.rxBytes * 8.0 / duration / 1e6 : 0;
std::cout << "Flow " << flow.first << " (" << t.sourceAddress << " -> "
<< t.destinationAddress << ")" << std::endl;
std::cout << " Tx Packets: " << s.txPackets << std::endl;
std::cout << " Rx Packets: " << s.rxPackets << std::endl;
std::cout << " Tx Bytes: " << s.txBytes << std::endl;
std::cout << " Rx Bytes: " << s.rxBytes << std::endl;
std::cout << " Lost: " << s.lostPackets << std::endl;
std::cout << " Duration: " << duration << " s" << std::endl;
std::cout << " Mean delay: "
<< (s.rxPackets ? s.delaySum.GetSeconds() / s.rxPackets * 1000 : 0)
<< " ms" << std::endl;
std::cout << " Throughput: " << throughput << " Mbit/s" << std::endl;
}
monitor->SerializeToXmlFile("throughput-" + delay + ".xml", false, false);
Simulator::Destroy();
return 0;
}Output
Expected console output for the 10 ms run (values are plausible for TCP NewReno on a 5 Mbit/s link, not from an actual run; your numbers will differ slightly):
Delay = 10ms
Flow 1 (10.1.1.1 -> 10.1.1.2)
Tx Packets: 9520
Rx Packets: 9512
Tx Bytes: 5484520
Rx Bytes: 5480000
Lost: 8
Duration: 9.0012 s
Mean delay: 26.4 ms
Throughput: 4.87 Mbit/s
Flow 2 (10.1.1.2 -> 10.1.1.1)
Tx Packets: 4760
Rx Packets: 4760
Tx Bytes: 190400
Rx Bytes: 190400
Lost: 0
Duration: 8.9905 s
Mean delay: 10.9 ms
Throughput: 0.17 Mbit/sTable for the record (flow 1, simulation 10 s, source active from 1 s):
| One-way delay | RTT | Rx Bytes (expected) | Duration | Throughput = 8 x Rx Bytes / duration |
|---|---|---|---|---|
| 1 ms | 2 ms | 5,520,000 | 9.00 s | 4.91 Mbit/s |
| 10 ms | 20 ms | 5,480,000 | 9.00 s | 4.87 Mbit/s |
| 50 ms | 100 ms | 5,290,000 | 9.00 s | 4.70 Mbit/s |
| 100 ms | 200 ms | 4,980,000 | 9.00 s | 4.42 Mbit/s |
Sample calculation for the 100 ms row: .
Why the curve falls: from the formula sheet, at 100 ms. NS-3’s default send and receive buffers are 128 kB, so the window can just cover the pipe and the steady state still fills the link; what is lost is the slow-start ramp. With the default initial window of 10 segments of 536 bytes, slow start needs about five RTTs (5.36, 10.7, 21.4, 42.9, 85.8, then 125 kB) to fill the pipe: 10 ms at RTT 2 ms, but one full second at RTT 200 ms. The window bound is still above the link rate, so the link, not the window, is the limit at every delay in the table. Run --delay=200ms as an extra row: the bound becomes 2.62 Mbit/s and the measured value drops below it.
Explanation
BulkSendHelper with MaxBytes 0 keeps the TCP socket’s send buffer full, so the connection runs as fast as congestion control and the link allow. PacketSinkHelper on n1 accepts the connection and discards data while counting bytes. FlowMonitorHelper::InstallAll attaches probes to the IPv4 layer of every node and groups packets by the five-tuple (addresses, ports, protocol) into flows; the TCP connection therefore appears as two flows, data and ACKs. The script computes the formula-sheet throughput, , from FlowStats. rxBytes counts IP packets including headers, which is why it is a little larger than what PacketSink::GetTotalRx (payload only) would report. Config::SetDefault for TcpNewReno makes the run match the congestion-window formulas on the sheet instead of the CUBIC default.
Question 4
Problem Statement
Write in lab recordCreate a simple network topology having two client node on left side and two server nodes on the right side. Both clients are connected with another node n1. Similarly, both server node connecting to node n2. Also connect node n1 and n2 thus forming a dumbbell shape topology. Use point to point link only.
Solution
Write in lab recordSteps
- Number the nodes so the code index equals the label: n0, n1 clients; n2, n3 routers (the manual’s n1 and n2); n4, n5 servers.
- Save the program as
scratch/dumbbell.ccand run./ns3 run scratch/dumbbell. - Check the address line and the routing table of n2 printed at 1 s: it must hold routes to 10.1.4.0 and 10.1.5.0 through 10.1.3.2.
- Check that both echo clients get their reply; the reply proves the packet crossed both routers and the bridge.
ls dumbbell-*.pcapshows ten files, one per device (EnablePcapAllcovers every point-to-point device). Opendumbbell-2-2.pcap, the n2 side of the bridge, and confirm both echo requests pass through it.
Program
/*
* MCSL-223 Section 1, Session 2, Question 4
* Dumbbell topology with point-to-point links only.
*
* Build: copy to ns-3.36+/scratch/ and run
* ./ns3 run scratch/dumbbell
*
* n0 (client) --\ /-- n4 (server)
* n2 ---- bridge ---- n3
* n1 (client) --/ 2 Mbps, 10 ms \-- n5 (server)
*
* Access links 10 Mbps, 1 ms. Subnets:
* n0-n2 10.1.1.0/24 n1-n2 10.1.2.0/24 n2-n3 10.1.3.0/24
* n3-n4 10.1.4.0/24 n3-n5 10.1.5.0/24
* The manual calls the routers n1 and n2; here they are n2 and n3 so the
* node index matches the NodeContainer index.
*/
#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"
using namespace ns3;
NS_LOG_COMPONENT_DEFINE("Dumbbell");
/*
* main: creates six nodes, five point-to-point links, five subnets,
* fills the routing tables with global routing and verifies the paths with
* one UDP echo from each client to its server.
*/
int
main(int argc, char* argv[])
{
CommandLine cmd(__FILE__);
cmd.Parse(argc, argv);
LogComponentEnable("UdpEchoClientApplication", LOG_LEVEL_INFO);
LogComponentEnable("UdpEchoServerApplication", LOG_LEVEL_INFO);
NodeContainer nodes;
nodes.Create(6);
// Node pairs for the five links.
NodeContainer n0n2(nodes.Get(0), nodes.Get(2));
NodeContainer n1n2(nodes.Get(1), nodes.Get(2));
NodeContainer n2n3(nodes.Get(2), nodes.Get(3));
NodeContainer n3n4(nodes.Get(3), nodes.Get(4));
NodeContainer n3n5(nodes.Get(3), nodes.Get(5));
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(n0n2);
NetDeviceContainer d1d2 = access.Install(n1n2);
NetDeviceContainer d2d3 = bridge.Install(n2n3);
NetDeviceContainer d3d4 = access.Install(n3n4);
NetDeviceContainer d3d5 = access.Install(n3n5);
InternetStackHelper stack;
stack.Install(nodes);
// One subnet per link.
Ipv4AddressHelper address;
address.SetBase("10.1.1.0", "255.255.255.0");
Ipv4InterfaceContainer i0i2 = address.Assign(d0d2);
address.SetBase("10.1.2.0", "255.255.255.0");
Ipv4InterfaceContainer i1i2 = address.Assign(d1d2);
address.SetBase("10.1.3.0", "255.255.255.0");
Ipv4InterfaceContainer i2i3 = 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);
// Routers n2 and n3 need routes to every subnet.
Ipv4GlobalRoutingHelper::PopulateRoutingTables();
std::cout << "n0 " << i0i2.GetAddress(0) << " n1 " << i1i2.GetAddress(0)
<< " n2 " << i0i2.GetAddress(1) << "/" << i1i2.GetAddress(1) << "/"
<< i2i3.GetAddress(0) << " n3 " << i2i3.GetAddress(1) << "/"
<< i3i4.GetAddress(0) << "/" << i3i5.GetAddress(0) << " n4 "
<< i3i4.GetAddress(1) << " n5 " << i3i5.GetAddress(1) << std::endl;
// Echo servers on n4 and n5; one echo from n0 to n4 and from n1 to n5.
UdpEchoServerHelper echoServer(9);
ApplicationContainer servers = echoServer.Install(NodeContainer(nodes.Get(4), nodes.Get(5)));
servers.Start(Seconds(1.0));
servers.Stop(Seconds(10.0));
UdpEchoClientHelper client0(i3i4.GetAddress(1), 9);
client0.SetAttribute("MaxPackets", UintegerValue(1));
client0.SetAttribute("PacketSize", UintegerValue(1024));
ApplicationContainer c0 = client0.Install(nodes.Get(0));
c0.Start(Seconds(2.0));
c0.Stop(Seconds(10.0));
UdpEchoClientHelper client1(i3i5.GetAddress(1), 9);
client1.SetAttribute("MaxPackets", UintegerValue(1));
client1.SetAttribute("PacketSize", UintegerValue(1024));
ApplicationContainer c1 = client1.Install(nodes.Get(1));
c1.Start(Seconds(3.0));
c1.Stop(Seconds(10.0));
// Print n2's routing table at 1 s to show the global routes.
Ptr<OutputStreamWrapper> routing = Create<OutputStreamWrapper>(&std::cout);
Ipv4GlobalRoutingHelper::PrintRoutingTableAt(Seconds(1.0), nodes.Get(2), routing);
bridge.EnablePcapAll("dumbbell");
Simulator::Stop(Seconds(10.0));
Simulator::Run();
Simulator::Destroy();
return 0;
}Output
Topology for the record:
n0 (10.1.1.1) ---10 Mbit/s, 1 ms---\ /---10 Mbit/s, 1 ms--- n4 (10.1.4.2)
n2 ---2 Mbit/s, 10 ms (bridge)--- n3
n1 (10.1.2.1) ---10 Mbit/s, 1 ms---/ \---10 Mbit/s, 1 ms--- n5 (10.1.5.2)
subnets: 10.1.1.0/24 (n0-n2) 10.1.2.0/24 (n1-n2) 10.1.3.0/24 (n2-n3)
10.1.4.0/24 (n3-n4) 10.1.5.0/24 (n3-n5)Expected console output (routing-table rows may print in a different order in your version; the echo time stamps are computed from the link parameters):
n0 10.1.1.1 n1 10.1.2.1 n2 10.1.1.2/10.1.2.2/10.1.3.1 n3 10.1.3.2/10.1.4.1/10.1.5.1 n4 10.1.4.2 n5 10.1.5.2
Node: 2, Time: +1s, Local time: +1s, Ipv4ListRouting table
Priority: 0 Protocol: ns3::Ipv4StaticRouting
Node: 2, Time: +1s, Local time: +1s, Ipv4StaticRouting table
Destination Gateway Genmask Flags Metric Ref Use Iface
127.0.0.0 0.0.0.0 255.0.0.0 U 0 - - 0
10.1.1.0 0.0.0.0 255.255.255.0 U 0 - - 1
10.1.2.0 0.0.0.0 255.255.255.0 U 0 - - 2
10.1.3.0 0.0.0.0 255.255.255.0 U 0 - - 3
Priority: -10 Protocol: ns3::Ipv4GlobalRouting
Node: 2, Time: +1s, Local time: +1s, Ipv4GlobalRouting table
Destination Gateway Genmask Flags Metric Ref Use Iface
10.1.3.2 10.1.3.2 255.255.255.255 UH - - - 3
10.1.4.1 10.1.3.2 255.255.255.255 UGH - - - 3
10.1.4.2 10.1.3.2 255.255.255.255 UGH - - - 3
10.1.5.1 10.1.3.2 255.255.255.255 UGH - - - 3
10.1.5.2 10.1.3.2 255.255.255.255 UGH - - - 3
10.1.4.0 10.1.3.2 255.255.255.0 UG - - - 3
10.1.5.0 10.1.3.2 255.255.255.0 UG - - - 3
At time +2s client sent 1024 bytes to 10.1.4.2 port 9
At time +2.0179s server received 1024 bytes from 10.1.1.1 port 49153
At time +2.0179s server sent 1024 bytes to 10.1.1.1 port 49153
At time +2.03581s client received 1024 bytes from 10.1.4.2 port 9
At time +3s client sent 1024 bytes to 10.1.5.2 port 9
At time +3.0179s server received 1024 bytes from 10.1.2.1 port 49153
At time +3.0179s server sent 1024 bytes to 10.1.2.1 port 49153
At time +3.03581s client received 1024 bytes from 10.1.5.2 port 9Delay calculation for one echo request (1054 bytes on the wire), hop by hop with the formula sheet:
| Hop | Sum | ||
|---|---|---|---|
| n0 to n2 (10 Mbit/s) | 0.843 ms | 1 ms | 1.843 ms |
| n2 to n3 (2 Mbit/s) | 4.216 ms | 10 ms | 14.216 ms |
| n3 to n4 (10 Mbit/s) | 0.843 ms | 1 ms | 1.843 ms |
| One way | 17.90 ms | ||
| Round trip | 35.81 ms |
The log shows the request at n4 at 2.0179 s and the reply at n0 at 2.03581 s, matching the table.
Explanation
Each PointToPointHelper::Install call takes a pair of nodes and produces two devices and one channel, so five calls build the five links; using two helpers lets the bridge have a lower rate and longer delay than the access links. Every link needs its own subnet because IPv4 forwarding chooses the outgoing interface by matching the destination against a network prefix; putting two links in one subnet would make that choice ambiguous. n2 and n3 have three interfaces each and forward because InternetStackHelper enables IP forwarding on every node. Without routes they would drop packets for 10.1.4.0 and 10.1.5.0; Ipv4GlobalRoutingHelper::PopulateRoutingTables reads the whole topology from the simulator (link-state style, like OSPF) and writes shortest-path routes into every node before the simulation starts. The bridge at 2 Mbit/s is the bottleneck that Sessions 3, 8 and 9 congest on purpose.
Viva Questions
Do not copy. Read for understanding and the viva- Q: Why does FlowMonitor show two flows for one TCP connection? A: Data and ACKs have swapped source and destination, so they are different five-tuples.
- Q: Why does throughput fall as delay rises even when the window covers the BDP? A: Slow start needs a fixed number of RTTs to open the window; longer RTT means more seconds at low rate.
- Q: State the bandwidth-delay product for 5 Mbit/s and RTT 200 ms. A: 10 to the 6 bits, 125 kB.
- Q: Why is
rxBytesfrom FlowMonitor larger thanGetTotalRxfrom the sink? A: FlowMonitor counts at the IP layer, headers included; the sink counts payload. - Q: Why does the dumbbell need five subnets? A: Each point-to-point link is a separate IP network; forwarding matches destination prefixes to interfaces.
- Q: What does
PopulateRoutingTablesdo? A: Builds a global link-state view of the topology and installs shortest-path routes in every node. - Q: What is the bottleneck of the dumbbell and where would packets be dropped? A: The 2 Mbit/s bridge; drops happen in the transmit queue of n2’s bridge device.
- Q: What is the one-way delay of a 1024-byte echo across the dumbbell? A: About 17.9 ms: 0.843 + 1 on each access link and 4.216 + 10 on the bridge.
Common Mistakes
Do not copy. Read for understanding and the viva- Reading throughput off the ACK flow (flow 2) instead of the data flow.
- Dividing by the simulation time (10 s) instead of the flow duration from first Tx to last Rx.
- Assigning the same subnet to two links, which makes routing fail silently.
- Forgetting
PopulateRoutingTables, so the echo client sends and nothing ever comes back. - Building the dumbbell with
CsmaHelper; the manual says point-to-point only. - Mixing up the manual’s router names (n1, n2) with the code indices (n2, n3) in the record without saying so.
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 recordthroughput_vs_latency.cclisting and the FlowMonitor output of the four runs (1, 10, 50, 100 ms)- Throughput-versus-delay table with the BDP working and the slow-start explanation
dumbbell.cclisting, the dumbbell diagram with five subnets, n2’s routing table and the two echo exchanges with the hop-by-hop delay table