Learning TRex: Generating 100GbE Line-Rate Traffic on a Mellanox ConnectX-5

FastNetMon

August 10, 2026

Close-up of a blue-tinted motherboard showing connectors and capacitors, with a diagonal pale banner reading 'GUEST POST' and 'LEARNING TREX' in the top-right and the Fast NetMon logo bottom-left.
Home FastNetMon Blog Learning TRex: Generating 100GbE Line-Rate Traffic on a Mellanox ConnectX-5
Portrait of a man with short dark hair and light stubble, wearing a dark gray T-shirt, facing the camera (circular crop).

This post is a repost of a technical blog originally published by Denys Haryachyy, shared here with permission as part of ongoing research and engineering work around FastNetMon’s inline traffic processing capabilities.

TL;DR. Cisco TRex 3.06 pushes full 100GbE line rate — 142 Mpps, 100.01 Gbps L1, tx_util 100 % — from one desktop Ryzen 7 5800X and a single ConnectX-5 Ex (PCIe Gen4). The rate is set by hardware, not tuning: 14 worker cores800×2 MB hugepages, and stateless streams with randomized source IPs. Why 142 and not 148.8 Mpps? A 64-byte Scapy frame is 68 bytes on the wire, so 100e9 ÷ (88×8) = 142 Mpps.

A traffic generator earns its keep by saturating the wire, so your measurements blame the device under test, not the test rig. Here’s how to get there with TRex 3.06 on a ConnectX-5.

TRex 3.06 generating 100GbE line rate (142 Mpps) from one Ryzen 7 5800X with a Mellanox ConnectX-5 Ex.
Figure 1: TRex 3.06 generating 100GbE line rate (142 Mpps) from one Ryzen 7 5800X with a ConnectX-5 Ex.

The Hardware

ComponentValue
CPUAMD Ryzen 7 5800X — 8 cores / 16 threads
NICMellanox ConnectX-5 Ex, 100 GbE, PCIe Gen4 x16
OSUbuntu 24.04, TRex in a privileged container
TRexv3.06

TRex is CPU-bound, so cores are the ceiling. Core 0 runs the master and latency threads; the remaining 14 threads are data cores that fill the NIC queues. A PCIe Gen4 NIC matters — on Gen3 the bus caps you before the cores do.

TRex thread layout: core 0 master, core 8 latency, 14 worker threads (1-7, 9-15) filling the NIC queues.
Figure 2: The 14-core TRex layout — one master, one latency thread, 14 workers driving the NIC queues.

Why 142 Mpps Is Line Rate (Not 148.8)

The textbook 100 GbE figure is 148.8 Mpps (a 64-byte wire frame: 64 + 20 bytes preamble/IFG = 84 = 672 bits). But Scapy’s Ether excludes the 4-byte FCS the NIC appends, so a 64-byte Scapy frame is 68 bytes on the wire:

A 64-byte Scapy frame is 68 bytes on the wire (it carries the FCS) - so 100GbE line rate is 142 Mpps, not 148.8.
Figure 3: A 64-byte Scapy frame is 68 bytes on the wire — so line rate is 142 Mpps, not 148.8.
68 B frame (64 Scapy + 4 FCS) + 8 preamble + 12 IFG = 88 B = 704 bits100e9 / 704 = 142.0 Mpps   <- line rate for these frames

TRex confirms it: 142.06 Mpps, 100.01 Gbps L1, tx_util 100 %. To hit the literal 148.8 Mpps, build a 60-byte Scapy frame so the wire frame is exactly 64 bytes.

TRex Configuration

/etc/trex_cfg.yaml — two ports and the 14-core layout:

- port_limit      : 2  version         : 2  interfaces      : ["0000:26:00.0", "0000:26:00.1"]  port_info       :    - ip          : "10.0.1.1"    - ip          : "10.0.2.2"  platform        :    master_thread_id  : 0    latency_thread_id : 8    dual_if           :      - socket  : 0        threads : [1,2,3,4,5,6,7,9,10,11,12,13,14,15]

The ConnectX-5 uses the mlx5 bifurcated driver — no vfio unbind, the kernel and TRex share the port. Flush the kernel IP off the data-plane port, then launch:

ip addr flush dev enp38s0f0np0      # port 0echo 800 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages./t-rex-64 -i --iom 0 --no-ofed-check -c 14

Streams: Where Line Rate Comes From

A single fixed 5-tuple pins to one queue (~10–15 Mpps). To spread across all 14 cores, vary the source IP randomly per packet:

vm = STLScVmRaw([    # random source IP - spreads RSS across all RX queues    STLVmFlowVar(name="src", min_value="10.1.0.1",                 max_value="10.1.15.254", size=4, op="random"),    STLVmWrFlowVar(fv_name="src", pkt_offset="IP.src"),    STLVmFixIpv4(offset="IP"),   # fix checksum after the rewrite])stream = STLStream(packet=STLPktBuilder(pkt=pkt, vm=vm),                   mode=STLTXCont(pps=25_000_000))

Two gotchas. First, use op="random", not op="inc": incrementing every FlowVar in lockstep walks the values down a diagonal, so the unique-flow count collapses to the largest single range instead of the product of the ranges.

op=inc makes flows walk a diagonal (few unique flows); op=random fills the grid (many unique flows).
Figure 4: op="inc" collapses flows onto a diagonal; op="random" fills the grid.

Second, rewriting IP.src invalidates the IPv4 header checksum, so STLVmFixIpv4 must recompute it — otherwise a checksum-validating receiver drops every frame. (UDP checksums are optional in IPv4, so UDP(chksum=0) saves a FlowVar.)

Spreading Across the RX Queues

Random source IPs aren’t just convenient — they spread the load evenly across every RX queue. The receiver steers each packet by hashing its source IP, so a stream of random sources lands on all 32 queues to within 0.1 % of each other. Every poll worker stays busy, and no single core caps the receiver.

Random source IPs spread evenly across all 32 RX queues, CoV 0.1 percent, every queue active.
Figure 5: Random source IPs spread the load flat across all 32 RX queues (measured: CoV 0.1%, no idle queue).

The Result

Port 0  tx_pps     142.06  Mpps        tx_bps_L1  100.01  Gbps        tx_util    100.06  %

Full 100 GbE line rate, sustained, from one desktop Ryzen and one ConnectX-5. tx_util 100 % means there are no idle bit-times left on the wire.

Summary

  1. Hardware sets the ceiling — Gen4 ConnectX-5 + 14 cores dedicated to TRex.
  2. Target 142 Mpps, not 148.8 (68-byte wire frames). Trust tx_util.
  3. TRex: trex_cfg.yaml with 14 threads, -c 14, 800×2 MB hugepages.
  4. Streams: randomized source IPs + STLVmFixIpv4 — they spread RSS evenly across every RX queue (measured 0.1% CoV).

References