How to block several million packets per second with XDP?
When you need to block packets XDP at line rate on a Linux server, eXpress Data Path (XDP) delivers the fastest kernel-based filtering available today. This 2026 guide explains how to leverage XDP's stateless hook point—anchored at the network driver layer—to drop, rate-limit, or allow millions of packets per second with sub-microsecond per-packet overhead, protecting game servers, API endpoints, and mission-critical infrastructure from volumetric attacks without offloading traffic to external scrubbing providers.
XDP programs execute before the kernel allocates a full socket buffer (sk_buff), eliminating the memory and CPU cost of packet cloning, conntrack lookups, and routing decisions for unwanted traffic. By attaching a single compiled eBPF program to a network interface and driving rule logic through BPF maps, administrators gain a programmable stateless firewall capable of sustaining several million packets per second on modern NICs—performance unattainable with traditional iptables or even nftables alone.
This guide covers XDP fundamentals, BPF map-driven rule engines, real-world deployment workflows, and the complementary role of stateful nftables for connection tracking. Whether you manage a fleet of game servers facing UDP amplification floods or operate latency-sensitive APIs under SYN-flood pressure, understanding how to block packets XDP-style will give you the fastest possible first line of defense directly on your Linux host.
Why Block Packets at the XDP Layer?
Traditional Linux firewalls—iptables, nftables, even newer nftables with hardware offload—process packets after the kernel has already invested CPU cycles in allocating the sk_buff structure, parsing headers, and routing lookups. When a volumetric DDoS attack sends five million packets per second toward a single port, that upstream work saturates CPU cores long before your firewall rules can drop the frames.
XDP hooks execute immediately after the NIC driver's receive routine, often within the same interrupt context or NAPI poll cycle. At this stage:
- The packet exists only as a raw frame descriptor in the driver's ring buffer.
- No sk_buff allocation, no conntrack state, no routing table walk.
- Your eBPF program reads headers directly from the linear packet data pointer.
- Return codes—
XDP_DROP,XDP_PASS,XDP_TX,XDP_REDIRECT,XDP_ABORTED—determine packet fate in nanoseconds.
This architecture means legitimate traffic passes through to the full network stack while attack packets vanish before consuming kernel resources. On hardware with native XDP offload (certain Netronome, Mellanox, and Intel NICs), the NIC firmware itself can execute the eBPF program, achieving wire-speed filtering at 100 Gbps line rate.
For most server workloads—game hosting, SaaS APIs, VoIP gateways—software XDP on commodity hardware already delivers orders-of-magnitude performance gains over netfilter alone. The key challenge shifts from "can my firewall handle the load?" to "how do I manage hundreds of dynamic rules without recompiling and reloading my XDP program every time a blacklist updates?"
The answer: BPF maps. A single XDP program reads rule sets, IP blacklists, port ranges, and rate-limit counters from kernel-resident hash maps and arrays, updated in real time from user space. This design enables a centralized control plane—such as PAKKT.io—to synchronize rule changes across a fleet of agents without service interruption or program recompilation.

Designing a Map-Driven XDP Rule Engine
A production-grade XDP firewall separates policy from program logic. The eBPF program itself remains static—compiled once, loaded once per interface—while all configuration lives in BPF maps:
| Map Type | Purpose | Example Key → Value |
| BPF_MAP_TYPE_HASH | IP blacklist / whitelist | IPv4 address → action (drop / pass) |
| BPF_MAP_TYPE_ARRAY | Port-range rules | Rule index → { port_min, port_max, protocol, action, rate_limit } |
| BPF_MAP_TYPE_PERCPU_HASH | Per-CPU rate-limit counters | (Source IP, port) → packet count, timestamp |
| BPF_MAP_TYPE_ARRAY | Global statistics | Counter index → packets_dropped, packets_passed, bytes_dropped |
When a packet arrives, the XDP program performs a fast linear scan (for up to ~256 rules) or hash lookup (for IP lists):
// Pseudo-code excerpt from a map-driven XDP program
struct rule {
__u16 port_min;
__u16 port_max;
__u8 protocol; // IPPROTO_TCP, IPPROTO_UDP, 0=any
__u8 action; // 0=drop, 1=rate_limit, 2=allow_only
__u32 max_pps;
};
__u32 key = ctx->rx_queue_index; // or rule index
struct rule *r = bpf_map_lookup_elem(&rules_map, &key);
if (!r) return XDP_PASS;
// Parse Ethernet → IPv4 → TCP/UDP headers…
// Compare dport against r->port_min / port_max
// If match and action == 0: return XDP_DROP;
// If action == 1: check per-source rate counter…
This approach allows a control agent to add, remove, or update rules by writing to /sys/fs/bpf/ pinned maps using bpftool or libbpf libraries, without touching the loaded program. The kernel's BPF verifier guarantees memory safety: the program cannot crash, loop indefinitely, or access arbitrary kernel memory.
Stateless Packet-Rate Limiting in XDP
Because XDP is stateless and executes in interrupt or softirq context, maintaining per-connection byte-rate accounting is impractical. Instead, XDP rate-limits count packets per second from a given source IP (or IP + port tuple). A per-CPU hash map stores a timestamp and packet count; on each lookup, the program checks if the current second has rolled over:
struct rate_state {
__u64 timestamp_ns;
__u32 pkt_count;
};
// Per-CPU map eliminates lock contention across cores
struct rate_state *state = bpf_map_lookup_elem(&rate_map, &src_ip);
__u64 now = bpf_ktime_get_ns();
if (!state || (now - state->timestamp_ns) > 1000000000ULL) {
// New second window
state->timestamp_ns = now;
state->pkt_count = 1;
return XDP_PASS;
}
if (state->pkt_count >= rule->max_pps) {
return XDP_DROP; // Exceeded limit
}
state->pkt_count++;
return XDP_PASS;
This logic handles millions of distinct flows efficiently because lookups occur in per-CPU hash tables with zero cross-CPU synchronization. For byte-rate or connection-rate enforcement, defer to the stateful nftables layer described later.
Minimum and Maximum Packet Size Filters
Volumetric attacks often send minimum-sized frames (64 bytes including Ethernet headers) to maximize packet-per-second load, or jumbo frames to saturate bandwidth. XDP can reject packets outside a configured size range before any protocol parsing:
__u32 pkt_len = (void *)ctx->data_end - (void *)ctx->data;
if (pkt_len < rule->min_size || pkt_len > rule->max_size) {
return XDP_DROP;
}
This trivial check stops reflection-amplification payloads (often very small UDP responses) and fragmentation-based exploits (abnormally large IP fragments) with negligible overhead.

Complementing XDP with Stateful nftables
XDP excels at high-speed, stateless decisions—drop all packets from blacklisted IPs, enforce global PPS caps, filter by port and protocol. It cannot, however, track TCP connection state, validate TCP flags, or apply per-connection byte-rate limits. For those tasks, nftables with connection tracking (conntrack) remains essential.
Modern deployments layer XDP and nftables in a defense-in-depth model:
- XDP layer (L2/L3): Drop obvious attack traffic—blacklisted IPs, out-of-range ports, excessive PPS from single sources—before sk_buff allocation.
- nftables layer (L3/L4): Enforce stateful rules—accept only
NEWconnections under a rate limit, drop invalid TCP flags, apply per-connection byte quotas usingquotaorlimitstatements.
By isolating PAKKT's nftables rules in a dedicated inet pakkt table, you avoid conflicts with Docker's nat table, fail2ban's chains, or iptables-persistent legacy rules. The kernel processes tables in priority order; the pakkt table can have higher priority to preempt other tables for critical ports.
Example: Stateful TCP SYN Flood Protection
An XDP rule might allow TCP port 25565 traffic under 100,000 PPS globally. The nftables layer then enforces per-source connection limits:
nft add table inet pakkt
nft add chain inet pakkt input { type filter hook input priority filter \; policy accept \; }
# Rate-limit NEW connections per source IP
nft add rule inet pakkt input tcp dport 25565 ct state new \
meter syn_limit { ip saddr limit rate 50/second burst 20 packets } accept
# Drop invalid TCP flag combinations
nft add rule inet pakkt input tcp flags \& (fin|syn|rst|ack) == fin|syn drop
nft add rule inet pakkt input tcp flags \& (fin|syn|rst|ack) == 0x0 drop
This two-tier design means a 10 Mpps UDP reflection attack is dropped in XDP (zero CPU impact beyond the driver RX path), while a sophisticated low-rate TCP SYN flood with spoofed sources is caught by conntrack meters in nftables. Neither layer blocks legitimate traffic, and each operates in its performance sweet spot.
Synchronizing IP Blacklists Across Layers
When an IP is blacklisted, both the XDP BPF map and an nftables named set should be updated to ensure coverage regardless of which layer the packet reaches first:
# Add to XDP map (via bpftool or libbpf)
bpftool map update name pakkt_blacklist key hex 0a 00 00 01 value hex 01
# Add to nftables set
nft add element inet pakkt blacklist { 10.0.0.1 }
nft add rule inet pakkt input ip saddr @blacklist drop
A centralized agent—such as the lightweight Go daemon provided by PAKKT.io—handles this synchronization automatically, exposing a single API endpoint to add or remove IPs and propagating the change to both kernel subsystems within milliseconds.
Deploying and Managing XDP Programs in Production
Compiling and loading an XDP program manually involves clang, LLVM, libbpf headers, and careful attention to kernel version compatibility. For production fleets, a management platform abstracts these steps:
- Single program per interface: Attach one XDP object (
.oELF file) to each NIC. Avoid chaining multiple programs unless your kernel supports BPF program links and your use case demands it. - Pinned maps: Pin all configuration and statistics maps to
/sys/fs/bpf/pakkt/so they persist across agent restarts and are accessible to monitoring tools. - Graceful updates: When the agent updates the XDP program (e.g., to patch a verifier compatibility issue), it should load the new program, transfer map file descriptors via BPF_OBJ_PIN/BPF_OBJ_GET, then atomically replace the old program using
bpf_set_link_xdp_fdwith theXDP_FLAGS_UPDATE_IF_NOEXISTflag cleared. - Telemetry export: Periodically read per-rule and global counters from BPF maps and ship them to a time-series database (TimescaleDB, Prometheus) for dashboards and alerting.
Real-World Workflow: Adding a New Port Rule
Imagine you launch a new game server on UDP port 27015 and want to allow only that port while rate-limiting inbound traffic to 50,000 PPS:
- Log into the PAKKT web panel (or call the PAKKT public API).
- Create a rule:
protocol=UDP, port_min=27015, port_max=27015, rule_type=allow_only, max_pps=50000. - The agent receives the configuration delta via mTLS heartbeat (every 30 seconds).
- Agent writes the new rule struct to the
rules_mapBPF array at the next available index. - XDP program immediately begins enforcing the rule; no reload, no connection drop.
- Panel dashboard updates within one minute, showing real-time PPS and drop metrics for port 27015.
This zero-touch workflow eliminates SSH access, manual bpftool commands, and the risk of syntax errors in production. Audit logs capture every rule change with timestamp and operator identity.
Observability: bpftool and Real-Time Metrics
For deep troubleshooting, bpftool remains invaluable. Inspect loaded programs and their map references:
bpftool prog show
bpftool map dump name pakkt_rules
bpftool map dump name pakkt_blacklist
bpftool map dump name pakkt_stats
Each map dump reveals current state—active rules, blacklisted IPs, per-rule packet and byte counters. Combine this with perf or bpftrace to profile program execution time and identify hot paths if CPU usage climbs unexpectedly.
PAKKT's centralized panel aggregates these metrics across all agents, overlaying GeoIP data (world map of source IPs, top countries), per-port histograms, and anomaly detection. When a sudden spike in dropped packets occurs, operators see which rule triggered, which source IPs contributed, and historical context—all without SSH or command-line queries.
Performance Considerations and Hardware Offload
Software XDP on a modern x86_64 server (e.g., Intel Xeon Scalable, AMD EPYC) can handle several million packets per second per core, depending on rule complexity and packet size. Factors that influence throughput:
- Number of rules: A linear scan through 256 rules introduces ~256 conditional branches per packet. For very large rule sets, consider a hash-map approach or hierarchical lookup (e.g., first match protocol, then port range).
- Map lookup latency: Hash map lookups are O(1) but not free; per-CPU maps eliminate lock contention at the cost of higher memory usage. Benchmark your specific workload with
bpf_map_lookup_elemtiming. - NIC driver support: Drivers with native XDP support (e.g.,
i40e,ixgbe,mlx5) outperform generic XDP, which operates after the driver allocates an sk_buff. - CPU core affinity: Use
ethtool -Lto configure NIC RX queues and pin them to specific CPU cores withirqbalancedisabled, reducing cache thrashing.
Hardware XDP Offload
Select NICs (Netronome NFP, certain Mellanox ConnectX) support offloading the eBPF program to the NIC's firmware or FPGA. In this mode, the program executes on the card itself, achieving line-rate filtering at 100 Gbps with zero host CPU involvement. Offload requires:
- Kernel 5.x or higher with
CONFIG_BPF_JIT_ALWAYS_ON. - Driver and firmware explicitly supporting XDP offload (
XDP_FLAGS_HW_MODE). - eBPF program adhering to stricter verifier constraints (no helper calls unsupported by the NIC).
For most server workloads, software XDP is sufficient and more flexible. Reserve hardware offload for edge cases—backbone routers, 100G-attached scrubbing appliances—where every CPU cycle counts.
Integrating XDP Protection with Existing Infrastructure
Operators often worry that adding XDP will conflict with Docker's iptables rules, fail2ban's dynamic chains, or existing iptables-persistent configurations. The good news: XDP and nftables coexist peacefully if you follow isolation best practices.
Docker and Container Networking
Docker manipulates the nat table (iptables/nftables) for port forwarding and masquerading. PAKKT's nftables rules live in a separate inet pakkt table with a defined priority. Packet flow:
- XDP hook (before sk_buff allocation).
- Netfilter
prerouting(Docker'snatrules). - Netfilter
input(PAKKT'spakkttable, fail2ban'sfiltertable).
Because XDP drops attack packets before netfilter, Docker never sees them. Legitimate traffic flows through XDP (verdict XDP_PASS), enters netfilter, undergoes NAT if needed, then matches PAKKT's stateful rules. Zero interference.
Fail2Ban and Dynamic IP Blocking
Fail2Ban traditionally adds iptables rules to block IPs after detecting login failures. To leverage XDP speed, configure Fail2Ban to also update PAKKT's blacklist map via a custom action script:
# /etc/fail2ban/action.d/pakkt-blacklist.conf
[Definition]
actionban = bpftool map update name pakkt_blacklist key hex value hex 01
actionunban = bpftool map delete name pakkt_blacklist key hex
Now banned IPs are dropped in XDP and netfilter, providing belt-and-suspenders protection. The PAKKT agent can also expose a local HTTP API for Fail2Ban to POST ban requests, automatically synchronizing to the panel's global blacklist if desired.
Panel Integrations: Pterodactyl, Pelican, WHMCS
Game server panels like Pterodactyl v1.x can embed PAKKT widgets—real-time attack graphs, one-click rule templates (e.g., "Minecraft protection preset"), IP whitelist management—directly in the server details view. The PAKKT API accepts an organization or server UUID, returning per-port metrics and allowing rule CRUD operations. Upcoming integrations for Pelican (Pterodactyl fork) and WHMCS will extend this to billing-driven auto-provisioning: a customer orders a game server, and PAKKT protection is automatically enabled with a default rule set. For implementation details, see PAKKT Integrations.
Conclusion
Blocking packets at the XDP layer transforms your Linux server into a high-performance stateless firewall capable of sustaining multi-million-packet-per-second attacks with negligible CPU overhead. By separating policy (BPF maps) from program logic (a single compiled XDP object), you gain the flexibility to update rules in real time without service disruption. Complementing XDP with stateful nftables for connection tracking and per-flow rate limiting delivers a complete kernel-level defense that coexists seamlessly with Docker, fail2ban, and existing firewall configurations. Whether you manage a single game server or a global fleet, mastering map-driven XDP engines and their integration into centralized management platforms will future-proof your network protection strategy for years to come.
FAQ
Can I use XDP to block packets based on payload content or Layer 7 headers?
XDP operates before the kernel parses Layer 7 data, and the eBPF verifier restricts unbounded loops, making deep packet inspection impractical. For HTTP header filtering or TLS SNI blocking, use a stateful firewall (nftables with conntrack) or a user-space proxy. XDP is best suited for L2/L3/L4 decisions—IP, port, protocol, packet size, and stateless rate-limiting.
How do I update an XDP program without dropping existing connections?
XDP is stateless and does not track connections; updating the program affects only new packets. To update, compile the new eBPF object, load it with bpf_set_link_xdp_fd using the XDP_FLAGS_UPDATE_IF_NOEXIST flag cleared (or use ip link set dev eth0 xdp off then xdp obj new.o). Ensure your BPF maps are pinned so the new program reattaches to the same configuration state. Existing TCP flows continue in nftables conntrack; XDP replacement is transparent to established connections.
What kernel version and NIC drivers are required for production XDP?
Kernel 5.x or higher is recommended for full BPF features and stable libbpf API. Native XDP (as opposed to generic XDP) requires driver support: i40e, ixgbe, mlx4, mlx5, nfp, and others listed in the kernel documentation at kernel.org. Verify with ethtool -i eth0 and check for XDP capabilities in driver release notes. Generic XDP works on any NIC but incurs higher latency since it operates after sk_buff allocation.
Deploy PAKKT in 30 seconds
Dual-layer kernel protection. XDP + nftables. Driven from a central panel. 7-day free trial.