Distributed microservice architectures, real-time media streaming platforms, and multi-region database clusters must withstand unpredictable wide-area network (WAN) conditions. Developing and testing software exclusively on pristine gigabit local networks masks critical failure modes: connection timeouts, head-of-line blocking, bufferbloat, and out-of-order packet reassembly flaws. To perform deterministic chaos engineering and network resilience validation, Linux provides Traffic Control (tc) paired with the Network Emulator (NetEm) kernel module.
Operating natively within the Linux kernel network scheduler, tc manipulates egress traffic queues through declarative queuing disciplines (qdiscs). By chaining NetEm onto physical or virtual network interfaces, system administrators and performance engineers can inject precise packet latency, Gaussian-distributed jitter, percentage-based packet loss, packet duplication, and strict token-bucket bandwidth rate limits without requiring external hardware network attenuators.
Architectural Overview: The Linux Traffic Control (tc) Subsystem
The Linux kernel network stack processes outbound IP packets through a structured egress hierarchy before frames are scheduled on physical network interface cards (NICs):
| Component | Kernel Mechanism | Operational Role |
|---|---|---|
| Queuing Discipline (qdisc) | Queue scheduler (e.g. pfifo_fast, fq_codel, netem) |
Manages the egress buffer queue; decides which packets are transmitted, delayed, or dropped. |
| Class (Classful qdiscs) | Bandwidth partitioning (e.g. HTB, CBQ) |
Subdivides available interface bandwidth into hierarchical traffic buckets with differing priorities. |
| Filter / Classifier | Traffic classification (e.g. u32, bpf) |
Directs outgoing packets into specific classes based on IP addresses, port numbers, or protocol flags. |
| NetEm Module | sch_netem kernel module |
An unclassed queuing discipline specifically designed to emulate variable network transmission impairments. |
| Token Bucket Filter (TBF) | sch_tbf rate limiter |
Controls byte throughput using a virtual token bucket, enforcing rigid peak rate limits and burst tolerances. |
Verifying Kernel Module Prerequisites
The sch_netem module must be compiled into the running kernel or loaded dynamically.
# Verify kernel module availability and load sch_netem
sudo modprobe sch_netem
# Confirm the module is active
lsmod | grep netem
# Inspect current default queuing disciplines on active interface (e.g. eth0)
ip link show dev eth0
tc qdisc show dev eth0
Simulating Latency and Normal Distribution Jitter
Simulating cross-continental fiber transit or high-latency satellite uplinks requires introducing deterministic round-trip delays and jitter.
Step 1: Apply Constant Network Latency
To inject an unconditional 100-millisecond delay to all egress packets on interface eth0:
# Attach netem qdisc to the root of the egress queue
sudo tc qdisc add dev eth0 root netem delay 100ms
# Verify latency using ping to an external test gateway
ping -c 4 1.1.1.1
Step 2: Inject Statistical Jitter with Gaussian Normal Distribution
Real-world networks rarely maintain perfectly constant latency; transit queues experience millisecond jitter. Apply 100ms base latency with ±20ms jitter following a normal (bell-curve) distribution:
# Modify active netem rule to include normal-distributed jitter
sudo tc qdisc change dev eth0 root netem delay 100ms 20ms distribution normal
Consecutive ICMP pings will now fluctuate naturally between 80ms and 120ms, realistically simulating congested public peering routes.
Injecting Packet Loss, Corruption, and Duplication
Lossy cellular networks and degraded Wi-Fi environments exhibit intermittent packet drops and corrupted bits.
Step 1: Emulating Independent Packet Loss
To drop exactly 5% of all outgoing packets:
# Apply 5% random packet loss
sudo tc qdisc change dev eth0 root netem loss 5%
Step 2: Simulating Burst Loss via the Gilbert-Elliot Model
Packet loss frequently occurs in bursts (e.g. transient radio fades). Specify a successive packet loss correlation percentage (e.g. 5% loss with a 25% correlation to the preceding packet state):
# Apply correlated burst packet loss
sudo tc qdisc change dev eth0 root netem loss 5% 25%
Step 3: Injecting Packet Corruption and Duplication
To test application-layer checksum verification and TCP sequence handling:
# Inject 1% packet duplication and 0.2% single-bit random corruption
sudo tc qdisc change dev eth0 root netem loss 2% duplicate 1% corrupt 0.2%
Combining Bandwidth Throttling (TBF) with NetEm
To simulate a constrained mobile connection (e.g. 2 Mbps uplink with 150ms latency), nest a NetEm qdisc underneath a Token Bucket Filter (TBF) rate limiter.
# Step 1: Create a root TBF qdisc rate-limiting egress traffic to 2mbit
sudo tc qdisc add dev eth0 root handle 1: tbf rate 2mbit burst 32kbit latency 400ms
# Step 2: Attach NetEm as a child qdisc to handle 1: injecting 150ms delay
sudo tc qdisc add dev eth0 parent 1:1 handle 10: netem delay 150ms 15ms loss 1%
Auditing Active Metrics and Resetting Configurations
Monitor packet transmission counters and remove network emulation rules once chaos experiments conclude.
Step 1: Audit Queue Statistics and Packet Counters
# Display detailed packet, drop, and backlog statistics
tc -s qdisc show dev eth0
The command outputs exact counts of transmitted bytes, packets delayed by NetEm, and packets deliberately dropped to satisfy loss rules.
Step 2: Flush and Reset Interface Queues to Default
# Remove root qdisc and restore default Linux queuing (e.g. fq_codel)
sudo tc qdisc del dev eth0 root
# Confirm interface is restored
tc qdisc show dev eth0
Troubleshooting Linux Traffic Control (tc) Failures
Consult this diagnostic matrix to resolve command syntax rejections and unexpected traffic behaviour:
| Error / Symptom | Underlying Root Cause | Corrective Action |
|---|---|---|
RTNETLINK answers: File exists |
Attempted to use tc qdisc add on an interface that already has a non-default root qdisc assigned. |
Use tc qdisc change to modify an existing rule, or delete the existing qdisc with tc qdisc del dev <iface> root before adding a new one. |
RTNETLINK answers: No such file or directory |
The sch_netem or sch_tbf kernel module is not loaded or missing from the kernel image. |
Execute sudo modprobe sch_netem. Ensure distribution kernel packages (linux-modules-extra) are installed. |
Ping shows latency on external hosts, but loopback (127.0.0.1) is unaffected. |
Rules applied to eth0 do not affect the local loopback interface. |
Apply tc commands explicitly to lo (e.g. sudo tc qdisc add dev lo root netem delay 50ms) for local inter-process testing. |
| Ingress traffic (downloads) is not delayed or dropped. | By design, Linux tc qdiscs only manage outbound (egress) queues. |
To shape ingress traffic, redirect incoming packets to an Intermediate Functional Block (IFB) pseudo-device using tc action mirred, then attach NetEm to the IFB interface. |
Leveraging tc and NetEm empowers engineers to systematically validate application resilience, verify timeout thresholds, and optimise distributed protocols against volatile real-world network conditions.