Skip to content
LinkState
Go back

Your Traffic Generator Placement Changes the Answer

Introduction

Traffic generation can be colocated with the device under test (DUT) on the same host, placed in the same rack, or located remotely. As the distance increases, the generator’s load becomes more isolated from the DUT, but the path introduces additional latency, jitter, and potential loss. In virtual forwarding benchmarks the first ceiling is often the generator’s ability to inject packets, not the dataplane’s forwarding capacity.


Same‑Host Traffic Generation

The generator and DUT share a compute node (VM, container, or bare‑metal). Traffic typically uses a virtual interface pair (veth, macvlan, Linux bridge) or the loopback interface. Because they compete for the same CPU, memory subsystem, and NIC queues, any limitation in the generator’s packet‑creation path directly caps the measured forwarding capacity.

Methodology

  1. Interface selection – Create a veth pair (veth0 ↔ veth1) or bind a macvlan interface to the host NIC.
  2. Network stack – Assign IP addresses to each end; the DUT (e.g., FRR) listens on one side, the generator transmits on the other.
  3. Packet generation – Use tools that can drive the NIC or kernel transmit path:
    • iperf3 for TCP/UDP bulk throughput.
    • pktgen (kernel module) for configurable rates, sizes, bursts.
    • MoonGen or TRex (DPDK‑based) for high‑rate, CPU‑isolated generation.
  4. CPU affinity – Pin the generator to a dedicated core (taskset or numactl) to avoid interference with DUT forwarding threads.
  5. Measurement – Capture byte counters on the NIC (ethtool -S <iface>) or inside the DUT (show interface in FRR).

Same‑Rack Traffic Generation

Generator and DUT reside on two distinct physical servers in the same rack, linked via a top‑of‑rack (ToR) switch. Traffic traverses at least one L2 hop and uses the physical NICs of both servers. Latency is low (sub‑µs to a few µs), but the generator now contends for NIC transmit queues, interrupt handling, and PCIe bandwidth independent of the DUT.

Methodology

  1. Physical connectivity – Connect servers to the same ToR switch via 10/25/40/100 GbE ports.
  2. IP addressing – Assign /30 or /31 subnets to the point‑to‑point links; optionally run OSPF/BGP to verify reachability.
  3. Generator choice – Prefer DPDK‑based generators (TRex, MoonGen) to bypass the kernel and saturate a 25 GbE NIC with minimal CPU overhead. If kernel tools are used, enable SO_REUSEPORT and increase txqueuelen.
  4. NIC tuning – Disable interrupt coalescing (ethtool -C <iface> rx-usecs 0 tx-usecs 0), set RSS to spread flows across queues, and enable LRO only if per‑packet timestamps are not required.
  5. Isolation – Reserve a NUMA node for the generator (numactl --cpunodebind=0 --membind=0) and another for the DUT to avoid cross‑node memory traffic.
  6. Validation – Verify lossless transmission with ping -f and measure latency with hping3 -i u1000.

Remote‑Host Traffic Generation

Generator and DUT are in different racks, data centers, or across a WAN emulator. Traffic crosses multiple switches/routers, incurring higher latency (tens of µs to ms) and jitter. The generator’s load is isolated from the DUT’s compute resources, but the path adds buffering, queuing, and possible packet loss.

Methodology

  1. Path emulation – Insert a latency/jitter emulator (netem on an intermediate Linux box) or a hardware WAN emulator to inject desired RTT (e.g., 50 ms) and jitter.
  2. Generator placement – Locate the generator on a dedicated traffic‑generation appliance or a server with a SmartNIC (e.g., Mellanox BlueField) to offload packet generation.
  3. Protocol considerations – For TCP benchmarks, enable congestion control suited to high‑BDP links (bbr or westwood+). For UDP, adjust packet size to avoid fragmentation (MTU discovery).
  4. Rate limiting – Use traffic shaping (tc qdisc add dev <iface> root tbf rate 10gbit burst 1542 latency 50ms) to prevent the generator from oversubscribing the emulated link, which would mask the DUT’s forwarding limit.
  5. Monitoring – Collect end‑to‑end metrics with tcpdump -w and post‑process with tcptrace or Wireshark to verify retransmits and out‑of‑order delivery.

Benchmarking Virtual Forwarding

Tools

ToolPlaneTypical UseKey Features
iperf3L4 (TCP/UDP)Bulk throughput, concurrent streamsSimple client/server, JSON output
netperfL4Request/response bulk, latencySupports TCP_RR, UDP_RR
pktgen (kernel)L2/L3Configurable packet rates, sizes, burstsDirect NIC access, CPU‑bound
TRexL2/L3/L4Stateful/stateless traffic, high rates (up to 2×100 GbE)DPDK, Lua scripting, latency histograms
MoonGenL2/L3Packet‑generator with DPDK, precise timingLuaJIT, hardware timestamping
OstinatoL2/L3/L4GUI‑driven, protocol‑aware streamsGood for emulating specific application traffic
FRR (as DUT)L3Routing, forwarding, ACLsRuns in Containerlab, reports forwarding counters

Benchmark Scenarios

ScenarioTopologyTraffic PatternMeasurement Point
Same‑hostContainerlab node with FRR + traffic‑gen container connected via veth pairUDP 64‑byte packets, 10 parallel streamsethtool -S veth0 on generator NIC; show interface in FRR
Same‑rackTwo bare‑metal servers in same rack, linked to ToR switch; FRR on server A, TRex on server BMixed IMIX (64/512/1518 B) at 10 Gbps targetNIC tx/rx counters on both servers; FRR show interface
Remote‑hostServer A (FRR) in Rack 1, Server B (TRex) in Rack 3, with netem adding 80 ms RTTTCP bulk (MSS 1460) with 30 concurrent connectionsServer‑side ss -i for cwnd/rtt; FRR forwarding counters; WAN emulator stats

Each scenario runs for a fixed duration (e.g., 60 s) and is repeated three times to obtain a mean and 95 % confidence interval. CPU utilization is sampled with pidstat 1 and memory with vmstat 1.


Identifying the Generator as the First Ceiling

Signs the Generator Is the Bottleneck

When these symptoms appear, the dataplane (virtual switch, OVS, or FRR forwarding path) is still under‑utilized; the first ceiling is the generator’s ability to feed packets into the network.

Troubleshooting Generator‑Related Bottlenecks

CLI Commands

# 1. Verify CPU affinity and utilization
pidstat -p $(pgrep -f trex) 1

# 2. Check NIC transmit queue drops
ethtool -S eth0 | grep -E 'tx_queue.*_stopped|tx_timeout'

# 3. Inspect interrupt distribution
cat /proc/interrupts | grep eth0

# 4. Look at packet generation stats (TRex example)
sudo trex-console -c show stats

# 5. Kernel pktgen interface (if using pktgen)
cat /proc/net/pktgen/kpktgend_0

Code Examples

TRex STL script – core pinning and large bursts

from trex.stl.api import *

class STLMix1(object):
    def get_stream(self, direction=0, **kwargs):
        # 64‑byte UDP packet, MAC swap per direction
        pkt = Ether()/IP(src="10.0.0.1", dst="10.0.0.2")/UDP(dport=1234, sport=5678)/Raw(b'x'*18)
        return STLStream(packet=STLPkt(pkt=pkt),
                         mode=STLTXCont(pps=10_000_000))  # 10 Mpps

    def get_streams(self, direction=0, **kwargs):
        return [self.get_stream(direction=direction)]

if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser(description='TRex STL streams')
    parser.add_argument('-c', '--core', type=int, default=2,
                        help='Core to pin TRex')
    args = parser.parse_args()
    from trex.stl.trex_stl_client import STLClient
    client = STLClient()
    client.connect()
    client.acquire(force=True)
    client.set_service_mode(cores=args.core)   # pins TRex to core 2
    client.add_streams(STLMix1().get_streams(), ports=[0])
    client.start(ports=[0], mult="10s")
    client.wait_on_traffic(ports=[0])
    client.get_stats()
    client.disconnect()

Key optimizations

Kernel pktgen – reduced interrupt load

# Load pktgen module
sudo modprobe pktgen

# Create a thread per CPU core to spread the load
for i in {0..3}; do
    echo "Adding device eth0 to cpu $i"
    echo eth0 > /proc/net/pktgen/kpktgend_$i
done

# Configure each thread
for i in {0..3}; do
    DEV=/proc/net/pktgen/kpktgend_$i
    echo "count 0" > $DEV               # run forever
    echo "clone_skb 1" > $DEV           # clone skb per packet (cheap)
    echo "pkt_size 64" > $DEV
    echo "dst_mac 00:11:22:33:44:55" > $DEV
    echo "dst_ip 10.0.0.2" > $DEV
    echo "dst_port 9" > $DEV            # discard port
    echo "flag QUEUE_MAP_CPU" > $DEV    # map queue to CPU
    echo "delay 0" > $DEV               # no inter‑packet delay (max rate)
done

# Start generation
echo "Start" > /proc/net/pktgen/pgctrl

Explanation – By creating a pktgen thread per core and enabling QUEUE_MAP_CPU, each core services a separate transmit queue, reducing per‑core interrupt pressure and allowing the NIC to sustain higher line‑rate before the generator becomes the limiter.


Scaling Limitations of Traffic Generators

Same‑Host Scaling Limitations

Same‑Rack Scaling Limitations

Remote‑Host Scaling Limitations


By recognizing these symptoms and applying the tuning steps above, you can isolate whether the traffic generator or the virtual dataplane is the first performance ceiling in your forwarding benchmarks.


Share this post on:

Previous Post
TI-LFA promised protection, the packet still died
Next Post
CLI, graph diff, or LLM for namespace triage