How to Use tc and netem to Delay Packets Only for a Specific IP Address in Linux


1 views

When trying to delay packets to a specific IP address using tc and netem, many developers encounter an issue where the delay affects all network traffic. Here's the typical problematic setup:

tc qdisc del dev eth0 root
tc qdisc add dev eth0 root handle 1: prio
tc qdisc add dev eth0 parent 1:1 handle 2: netem delay 500ms
tc filter add dev eth0 parent 1:0 protocol ip pref 55 handle ::55 u32 match ip dst 1.2.3.4 flowid 2:1

The key issue is that without a default rule, all unmatched traffic will fall through to the first available class. We need to explicitly create a bypass for normal traffic:

tc qdisc del dev eth0 root
tc qdisc add dev eth0 root handle 1: prio bands 3
tc qdisc add dev eth0 parent 1:1 handle 10: netem delay 500ms
tc qdisc add dev eth0 parent 1:2 handle 20: sfq
tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip dst 1.2.3.4 flowid 1:1
tc filter add dev eth0 protocol ip parent 1:0 prio 2 u32 match u32 0 0 flowid 1:2

Here's why this works:
1. We create a priority qdisc with 3 bands (though we only need 2)
2. Band 1 (1:1) handles our delayed traffic
3. Band 2 (1:2) handles all other traffic with simple fair queuing (sfq)
4. The first filter matches our target IP and sends it to Band 1
5. The second filter is a catch-all that sends everything else to Band 2

To confirm it's working, use these commands:

tc -s qdisc show dev eth0
tc -s filter show dev eth0

Then test with ping:

ping 1.2.3.4    # Should show ~500ms delay
ping 8.8.8.8    # Should show normal latency

For more complex scenarios, you might want to:

# Add jitter to the delay
tc qdisc change dev eth0 parent 1:1 handle 10: netem delay 500ms 100ms

# Limit the bandwidth for the target IP
tc qdisc add dev eth0 parent 1:1 handle 10: netem delay 500ms rate 1mbit

# Combine multiple conditions
tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32 \
    match ip dst 1.2.3.4 \
    match ip sport 80 0xffff \
    flowid 1:1

Remember to clean up your rules when done:

tc qdisc del dev eth0 root

When working with network traffic shaping on Linux systems, a common requirement is to apply latency to packets destined for a specific IP address while leaving other traffic unaffected. The initial approach using tc and netem often results in unintended global packet delays due to missing proper traffic classification.

Here's the proper sequence of commands to delay only traffic to 1.2.3.4:

# Clear existing qdisc
tc qdisc del dev eth0 root

# Create priority queue with three bands
tc qdisc add dev eth0 root handle 1: prio bands 3

# Add netem delay only to band 1
tc qdisc add dev eth0 parent 1:1 handle 10: netem delay 500ms

# Add basic filter for our target IP
tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32 \
    match ip dst 1.2.3.4 flowid 1:1

# Optional: Default filter for all other traffic (goes to band 3)
tc filter add dev eth0 parent 1:0 protocol ip prio 2 u32 \
    match ip dst 0.0.0.0/0 flowid 1:3

The critical differences from the original approach are:

  • Explicitly specifying bands 3 in the prio qdisc
  • Adding a default filter to catch unmatched traffic
  • Using proper flowid mapping (1:1 for delayed traffic, 1:3 for normal traffic)

To verify the setup is working correctly:

# Check qdisc hierarchy
tc -s qdisc show dev eth0

# Check filter rules
tc -s filter show dev eth0

# Test with ping (should see 500ms delay)
ping 1.2.3.4

# Test with other IP (should have normal latency)
ping 8.8.8.8

For more complex filtering needs, you can use advanced classifiers:

# Delay traffic to specific IP on specific port
tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32 \
    match ip dst 1.2.3.4 \
    match ip dport 80 0xffff \
    flowid 1:1