Transport Layer & TCP Variants: Analyze TCP goodput vs. offered load in a 10 Gbps link with controlled packet loss | NS3 Project 22
Transport Layer & TCP Variants: Analyze TCP goodput vs. offered load in a 10 Gbps link with controlled packet loss | NS3 Project 22
Aim:
To analyze TCP goodput vs. offered load over a 10 Gbps link under controlled packet loss conditions, using NS-3 simulation. The goal is to observe how increasing offered load impacts actual useful throughput (goodput) for different TCP variants. Also visualize packet flow using NetAnim.
Network Topology:
Two nodes:
·
n0 → Sender
·
n1 → Receiver
Link:
·
Bandwidth: 10 Gbps
·
Delay: 2 ms
·
TCP Bulk transfer with
controlled packet loss
n0 ---------------------- n1
10 Gbps, 2 ms delay
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/error-model.h"
#include "ns3/flow-monitor-module.h"
#include "ns3/netanim-module.h"
#include <fstream>
#include <string>
using namespace ns3;
NS_LOG_COMPONENT_DEFINE ("TcpGoodputExample");
double RunSimulation(double loadGbps)
{
NodeContainer nodes;
nodes.Create(2);
Config::SetDefault("ns3::TcpSocket::SegmentSize", UintegerValue(8960));
Config::SetDefault("ns3::TcpSocket::RcvBufSize", UintegerValue(1 << 26)); // 64MB
Config::SetDefault("ns3::TcpSocket::SndBufSize", UintegerValue(1 << 26)); // 64MB
Config::SetDefault("ns3::TcpSocketBase::WindowScaling", BooleanValue(true));
Config::SetDefault("ns3::TcpL4Protocol::SocketType", StringValue("ns3::TcpNewReno"));
//Config::SetDefault("ns3::TcpL4Protocol::SocketType", TypeIdValue(TcpCubic::GetTypeId())); or Bbr
PointToPointHelper p2p;
p2p.SetDeviceAttribute("DataRate", StringValue("10Gbps"));
p2p.SetChannelAttribute("Delay", StringValue("2ms"));
p2p.SetQueue("ns3::DropTailQueue", "MaxSize", StringValue("30000p"));
NetDeviceContainer devices = p2p.Install(nodes);
// --- Controlled Packet Loss ---
Ptr<RateErrorModel> em = CreateObject<RateErrorModel>();
em->SetAttribute("ErrorRate", DoubleValue(10e-9));
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 = 8080;
// --- Sink Application (Receiver) ---
PacketSinkHelper sinkHelper("ns3::TcpSocketFactory",
InetSocketAddress(Ipv4Address::GetAny(), port));
ApplicationContainer sinkApp = sinkHelper.Install(nodes.Get(1));
sinkApp.Start(Seconds(0.0));
sinkApp.Stop(Seconds(30.0));
// --- Source Application (Sender) ---
OnOffHelper sourceHelper("ns3::TcpSocketFactory",
InetSocketAddress(interfaces.GetAddress(1), port));
std::ostringstream rate;
rate << loadGbps << "Gbps";
sourceHelper.SetAttribute("DataRate", StringValue(rate.str()));
sourceHelper.SetAttribute("PacketSize", UintegerValue(8960));
sourceHelper.SetAttribute("OnTime", StringValue("ns3::ConstantRandomVariable[Constant=1]"));
sourceHelper.SetAttribute("OffTime", StringValue("ns3::ConstantRandomVariable[Constant=0]"));
ApplicationContainer sourceApp = sourceHelper.Install(nodes.Get(0));
sourceApp.Start(Seconds(1.0)); // Starts at 1s
sourceApp.Stop(Seconds(30.0)); // Stops at 30s
// --- Simulation Execution ---
Simulator::Stop(Seconds(30.1));
Simulator::Run();
// --- Goodput Calculation ---
// We access the Sink application directly to see how many bytes were delivered
Ptr<PacketSink> sink = DynamicCast<PacketSink>(sinkApp.Get(0));
uint64_t totalBytesReceived = sink->GetTotalRx();
// Duration is 29 seconds (from 1.0 to 30.0)
double duration = 29.0;
double goodputGbps = (totalBytesReceived * 8.0) / (duration * 1e9);
Simulator::Destroy();
return goodputGbps;
}
int main(int argc, char *argv[])
{
std::ofstream outFile;
outFile.open("load_vs_goodput.dat", std::ios::trunc);
outFile << "# Load(Gbps) Goodput(Gbps)" << std::endl;
double loads[] = {1, 2, 4, 6, 8, 10};
for (double load : loads)
{
std::cout << "Running simulation for Load: " << load << " Gbps..." << std::flush;
double goodput = RunSimulation(load);
outFile << load << " " << goodput << std::endl;
std::cout << " Done. Goodput: " << goodput << " Gbps" << std::endl;
}
outFile.close();
std::cout << "\nResults saved to load_vs_goodput.dat\n";
return 0;
}
Output:
tcpnewreno:
# Load(Gbps) Goodput(Gbps)
1 0.953423
2 1.35257
4 1.31719
6 1.24898
8 1.14828
10 1.26265
tcpcubic:
# Load(Gbps) Goodput(Gbps)
1 0.999862
2 1.9007
4 1.81446
6 1.67706
8 1.74666
10 1.80455
tcpbbr:
# Load(Gbps) Goodput(Gbps)
1 0.999862
2 1.99972
4 3.99942
6 4.00503
8 4.00497
10 4.00574
Graph:
da3.plt
set terminal png
set output 'graph.png'
set title "Load vs
Goodput"
set xlabel "Offered Load
(Gbps)"
set ylabel "Goodput
(Gbps)"
plot "load_vs_goodput.dat"
with linespoints title "NewReno", \
"load_vs_goodput2.dat" with linespoints title
"Cubic", \
"load_vs_goodput3.dat" with linespoints title "BBR"
Inference:
1.
The "Linear Scaling" Region
At the 1 Gbps mark, the graph shows
all three protocols overlapping perfectly.
·
Analysis:
When the offered load is low, the time between packet loss events is long
enough for even the most inefficient protocol (NewReno) to recover and deliver
100% of the traffic. At this stage, performance is limited by the Application
Source, not the network protocol.
2. The Efficiency
Divergence(The “Ceiling” Effect)
As the load surpasses
2 Gbps, we see a sharp divergence. This plateau represents the maximum
"steady-state" throughput each protocol can maintain under a 10−9
BER.
·
NewReno
(The Floor): Stabilizes at ~1.3 Gbps. The linear "Additive Increase"
is simply too slow to reclaim the bandwidth before the next random bit error
occurs. It is trapped in a perpetual state of recovery.
·
Cubic (The
Middle Ground): Stabilises at ~1.8 - 1.9 Gbps. Using a cubic window growth function yields higher speeds than Reno. However, it still falls victim
to the "loss-equals-congestion" fallacy, causing it to throttle
itself unnecessarily.
·
BBR (The
Leader): Stabilises at ~4.0 Gbps. Because it models the pipe (Bandwidth/Delay)
rather than reacting to individual losses, it ignores the "noise" of
bit errors. Its ceiling here is likely defined by the Socket Buffer/BDP limit
or internal ns-3 pacing rather than the error rate.
3. Saturation and "Congestive Collapse"
Looking at the 8 Gbps
to 10 Gbps range on the graph, there is a slight downward trend in Goodput for
NewReno and Cubic.
·
Analysis:
This is a "Saturation Degradation." As you increase the offered load,
you pump more packets into the wire per second. Statistically, this increases
the number of bit errors encountered per second.
· Inference: For loss-based protocols (Reno/Cubic), more frequent errors mean more frequent window-halving events. This leads to a state where the protocol spends more time in "recovery" than in "data transfer," causing the actual goodput to drop even as you try to send more data.
Key
Takeaway:
The simulation
demonstrates that bandwidth is not the bottleneck—protocol logic is. 1. NewReno
is obsolete for 10 Gbps links with any degree of noise. 2. Cubic offers a 45%
improvement over Reno but still leaves ~80% of the link capacity unused. 3. BBR
is the only variant capable of maintaining high-speed throughput in
"noisy" environments, outperforming NewReno by ~300%.
Comments
Post a Comment