
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. TRex’s stateless mode can simulate every common volumetric DDoS class at full 100 GbE line rate (142 Mpps), and they are all one generator template with a different header. This post takes each attack type in turn — what it is in the real world, how much of today’s traffic it is, and how to generate it in TRex. The shared trick is TRex’s Field Engine, which rewrites the source IP per packet to mimic the spoofed sources of a real botnet.
To test a DDoS-mitigation device I need attack traffic that looks like the real thing: high volume and spoofed, varied sources. TRex’s stateless mode gives me both — line-rate packet generation plus per-packet field rewriting — without modelling real TCP state. Each attack below is a real-world flood class paired with the exact snippet that generates it.
The Common Base: Spoofed Source IPs
Every flood below shares one thing: the source IP is randomised per packet to mimic a botnet of spoofed sources. That is a Field Engine program attached to the stream, and I reuse the exact same vm for all of them:
# Random (spoofed) source IP across a /16, with a fixed checksum
vm = STLScVmRaw([
STLVmFlowVar(name="src", min_value="10.1.0.1",
max_value="10.1.255.254", size=4, op="random"),
STLVmWrFlowVar(fv_name="src", pkt_offset="IP.src"),
STLVmFixIpv4(offset="IP"), # recompute IPv4 checksum
])
Two reasons this matters. Spoofed sources defeat per-source rate limits — no single IP stands out — and random sources spread across every RX queue of the target via RSS, so the attack hits all of the victim’s cores, not just one. Use op="random", not inc: random gives the full product of the address range as unique flows.
The Attacks at a Glance
Before the snippets, here is roughly how much of real network-layer DDoS each class accounts for — because it decides which ones are worth simulating first. These are Cloudflare’s 2025 Q2 per-vector shares; the ranking reshuffles every quarter, but the shortlist does not.

UDP Flood — and Its Reflection Variant
The attack. UDP has no handshake, so there is nothing to slow a flood down and no state to validate a spoofed source. Direct UDP floods are about 13% of network-layer attacks, but the bigger story is reflection: DNS floods alone are 33%, the single largest vector. In a reflection attack the attacker spoofs the victim’s address as the source of a small query to an open server, and the much larger reply is delivered to the victim — free bandwidth, and the attacker never touches the target.

| protocol | port | amplification | still common? |
|---|---|---|---|
| memcached | 11211 | up to 51,000× | mostly filtered since 2018 |
| NTP (monlist) | 123 | 557× | yes |
| DNS | 53 | 28–54× | yes — #1 vector |
| CLDAP | 389 | ~56× | yes |
| SSDP | 1900 | 31× | yes |
Reflection has one convenient property for a defender: it arrives with a well-known source PORT, so a single match catches it.
In TRex. The direct flood is the base packet with a UDP header; the reflection variant is a reply-shaped packet — source port 53, large payload — aimed straight at the target:
victim = "198.51.100.1"
# Direct UDP flood — connectionless, no handshake to slow it down
udp = Ether()/IP(dst=victim)/UDP(sport=1025, dport=53)
# Reflection variant — the packet LOOKS like a DNS reply arriving at the
# victim: source PORT 53, large payload. In the wild the attacker spoofs the
# victim as the source and an open resolver sends this; in the lab I just
# emit the reply-shaped packet straight at the target.
refl = (Ether()/IP(dst=victim)/UDP(sport=53, dport=1025)
/ (b"\x00" * 1400))
TCP SYN Flood
The attack. The second-largest vector at ~27%. Each SYN asks the target to allocate half-open connection state; at 142 Mpps the listen backlog and the conntrack table are the pressure point long before bandwidth is.

In TRex. Same base, swap the header for a TCP SYN:
# TCP SYN flood — half-open connections exhaust the listen backlog syn = Ether()/IP(dst=victim)/TCP(sport=1025, dport=80, flags="S")
TCP ACK Flood
The attack. An ACK with no prior SYN. A naive “drop non-SYN” rule will not catch it, and a stateful firewall still has to look up — and fail to find — a connection for every single packet. It is how attackers probe whether a filter is stateless or stateful.

In TRex. One flag changes:
# TCP ACK flood — an ACK with no prior SYN; slips past "drop non-SYN" rules ack = Ether()/IP(dst=victim)/TCP(sport=1025, dport=80, flags="A")
ICMP Echo Flood
The attack. The classic ping flood — pure volumetric pressure with no protocol subtlety. Its realism comes entirely from the spoofed-source spread, which is why the shared vm is doing all the work here.

In TRex. Swap in an ICMP echo header:
# ICMP echo flood — the classic ping flood icmp = Ether()/IP(dst=victim)/ICMP(type=8, code=0)
IP Fragment Flood
The attack. Send only the first fragment (MF=1) of an oversized datagram, so the target holds reassembly buffers waiting for a remainder that never arrives. The sharp edge for a filter is that a non-first fragment carries no L4 ports at all, so every rule that matches on ports is blind to it — a gap worth testing deliberately.

In TRex. Emit only the first fragment:
# IP-fragment flood — first fragment only (MF=1), forces the target to # hold reassembly state for a datagram whose rest never arrives. Note there # is no L4 port here, so any rule that matches on ports cannot classify it. frag = Ether()/IP(dst=victim, flags="MF", frag=0)/UDP(dport=53)
Carpet Bombing: Spread the Same Traffic Thinner
The attack. Not a new protocol — a targeting change. Send the same total volume, but spread it across an entire prefix instead of one host. No per-destination threshold trips, because no single host looks abnormal, and detection that works per-host misses it entirely. It has grown over 110% in recent tracking.

In TRex. Add a second Field Engine variable that walks the destination across the protected range — inc here, not random, so every address is hit evenly:
# Carpet bomb — same flood, but the DESTINATION walks a whole /16 so no
# single host looks abnormal. Add a second Field Engine variable for dst.
carpet_vm = STLScVmRaw([
STLVmFlowVar(name="src", min_value="10.1.0.1", max_value="10.1.255.254",
size=4, op="random"),
STLVmWrFlowVar(fv_name="src", pkt_offset="IP.src"),
STLVmFlowVar(name="dst", min_value="198.51.100.1",
max_value="198.51.100.254", size=4, op="inc", step=1),
STLVmWrFlowVar(fv_name="dst", pkt_offset="IP.dst"),
STLVmFixIpv4(offset="IP"),
])
Multi-Vector: All at Once
The attack. Real incidents rarely use one vector — attackers combine them precisely because defences tend to be vector-specific. The subtle part is the accounting. If I budget five vectors by equal bandwidth, they do not carry equal packets: the small-frame vectors dominate the packet rate, which is what actually costs a filter CPU.

In TRex. Build one stream per vector, with the pps of each derived from its frame size so the sum is exactly line rate:
# Multi-vector — several classes at once, budgeted by BANDWIDTH so each
# gets an equal share of the 100G line (equal pps would oversubscribe the
# moment frame sizes differ). pps = share_of_line / frame_bits.
line_bps = 100e9
def pps_for(frame_bytes, vectors):
return (line_bps / vectors) / ((frame_bytes + 24) * 8)
streams = []
for pkt, size in [(udp, 64), (syn, 64), (icmp, 64), (frag, 256), (refl, 1500)]:
streams.append(STLStream(packet=STLPktBuilder(pkt=pkt, vm=vm),
mode=STLTXCont(pps=pps_for(size, 5))))
Driving It at Line Rate
Whichever attack I picked, the last step is the same — wrap the packet and its vm in a stream and start at 100%:

s = STLStream(packet=STLPktBuilder(pkt=syn, vm=vm),
mode=STLTXCont(pps=25_000_000))
c.add_streams([s], ports=[0])
c.start(ports=[0], mult="100%", force=True)
Three such streams to different victim prefixes saturate a 100 GbE link at 142 Mpps of 64-byte frames. (Getting to that line rate — cores, hugepages, and the frame-size math — is its own topic.)
What You Will Actually See
One last piece of perspective, because it changes what you optimise for. The attacks that make the news are nothing like the attacks that happen. The overwhelming majority of real network-layer DDoS is small and brief:

So mitigation has to be automatic (ten minutes is not enough time for a human to react), but capacity cannot be planned off the median (the same infrastructure has to survive the rare hyper-volumetric one). Both numbers matter, and a line-rate generator is how you test against each end of that range.
Summary
- TRex stateless mode simulates every common volumetric DDoS class at 100 GbE line rate (142 Mpps).
- UDP/reflection, SYN, ACK, ICMP, and fragment floods are one template with a swapped header; the realism is spoofed source IPs via
STLVmFlowVar(op="random")+STLVmFixIpv4. - By attack count, DNS floods (33%) and SYN floods (27%) lead; UDP-carried vectors are about half, driven by reflection/amplification up to 51,000×.
- Carpet bombing is a targeting change (walk the destination prefix); multi-vector must be budgeted by bandwidth, because that is not the same as packet rate.
- Each class stresses a different resource — backlog, conntrack, reassembly buffers, or raw packet rate — so a real test suite needs all of them.
References
- Cisco TRex documentation — the generator and its stateless mode.
- TRex Stateless Python API —
STLStream,STLTXCont, and the Field Engine (STLVm*). - Cloudflare DDoS threat report, 2025 Q2 — the per-vector percentage shares and size/duration data.
- US-CERT TA14-017A — the canonical amplification-factor table.
- RFC 4987 — TCP SYN Flooding and MITRE ATT&CK T1498 — the attack descriptions.
- RFC 9411 — benchmarking methodology for network-security-device performance.
- NETSCOUT ASERT: Carpet-Bombing — how prefix-wide attacks evade per-host detection, and their growth.
- 360 Netlab: Memcache DDoS — measurement of the 51,000× memcached amplification factor.
- Cloudflare DDoS threat report, 2025 Q4 — the 31.4 Tbps record and the full-year totals.






