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 Characteristics:
- Bandwidth: 10 Gbps
- Delay: 2 ms
- Traffic Type: 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));
devices.Get(1)->SetAttribute("ReceiveErrorModel", PointerValue(em));
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 (Gnuplot Script):
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 almost perfectly.
- Analysis: When the offered load is low, the time between packet loss events is long enough for even the most conservative loss-based protocol (NewReno) to recover and deliver 100% of the traffic. At this stage, performance is limited by the Application Source rate, not by network congestion or protocol limits.
2. The Efficiency Divergence (The "Ceiling" Effect)
As the load surpasses 2 Gbps, a sharp divergence emerges. This plateau represents the maximum steady-state throughput each protocol can maintain under a non-zero bit error rate (BER):
- NewReno (The Floor): Stabilizes at ~1.3 Gbps. The standard linear Additive Increase (AIMD) is too slow to reclaim the 10 Gbps pipe before the next random loss occurs, leaving it trapped in frequent recovery cycles.
- Cubic (The Middle Ground): Stabilizes at ~1.8 – 1.9 Gbps. Using a cubic window growth function allows it to ramp up faster than NewReno. However, because it still treats packet loss as congestion, random link errors cause unnecessary window reductions.
- BBR (The Leader): Stabilizes at ~4.0 Gbps. Because BBR models the network path (estimated bottleneck bandwidth and minimum RTT) rather than halving its window on individual packet losses, it ignores non-congestive bit errors. Its ceiling here is constrained by the BDP/socket buffer limits rather than error rate sensitivity.
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: As the offered load increases, more packets are injected per second. Statistically, a constant packet error rate results in a higher absolute number of packet drops per unit time.
- Inference: For loss-based protocols (NewReno/Cubic), more frequent packet drop triggers mean more frequent window-halving events. Consequently, the protocol spends more time in recovery than in steady transmission, causing goodput to plateau or degrade as offered load increases.
Key Takeaway:
The simulation demonstrates that raw link bandwidth alone does not guarantee high throughput—congestion control logic plays the decisive role:
- NewReno is severely limited on high-speed (10 Gbps) links in the presence of random packet loss.
- Cubic improves performance over NewReno, but still underutilizes high-bandwidth links when packet losses are non-congestive.
- BBR sustains significantly higher goodput in lossy high-speed environments by decoupling congestion estimation from loss detection.
Comments
Post a Comment