XDP / eBPF

How to Write Your First XDP Rule to Block a Port in 2026?

October 16, 2026 · 9 min read
Illustration immersive du sujet : XDP rule block port

When you need to block a port with an XDP rule, you bypass the entire kernel network stack and drop unwanted packets at the earliest possible stage—right in the network driver. This kernel-level filtering technique, powered by eBPF programs attached to XDP hooks, delivers sub-microsecond per-packet processing and can sustain traffic loads that would overwhelm traditional iptables or even nftables alone. In 2026, XDP has matured into the de facto standard for high-performance packet filtering on Linux servers exposed to volumetric attacks, port scans, and application-layer floods.

This article explains why and how to block ports using XDP, compares XDP to nftables for port-based rules, walks through the technical flow from NIC to BPF map, and shows you how to orchestrate XDP port blocks at scale without writing C code or recompiling programs for every rule change.



Why Block a Port at the XDP Layer

Traditional firewalls—whether iptables, nftables, or firewalld—operate inside the kernel network stack, after the skb (socket buffer) has been allocated and populated. Every packet, even one destined for a closed port or a malicious scanner, consumes CPU cycles in skb allocation, protocol parsing, conntrack lookup, and ruleset traversal before being dropped. Under a sustained volumetric attack reaching millions of packets per second, this overhead can saturate CPU cores, exhaust conntrack table slots, and degrade legitimate traffic.

XDP (eXpress Data Path) attaches an eBPF program to the network interface at the driver layer, before any skb allocation. The program inspects raw Ethernet frames in a per-CPU ring buffer and returns a verdict—XDP_DROP, XDP_PASS, XDP_TX, or XDP_REDIRECT—within nanoseconds. Dropping a packet at XDP means:

  • No skb allocation or memory copy
  • No traversal of netfilter hooks
  • No conntrack state creation
  • Immediate return of the buffer to the driver's RX ring

This architecture enables a single server to drop several million packets per second per core with negligible latency impact on passed traffic. For game servers, API endpoints, or any UDP/TCP service under DDoS, blocking unwanted ports at XDP is the difference between staying online and being overwhelmed.



How XDP Rule Block Port Logic Works

An XDP program is a small eBPF bytecode binary loaded into the kernel and attached to a network interface. The program runs every time a packet arrives, before the kernel sees it. To block a port, the program must:

  1. Parse the Ethernet header to determine if the frame is IPv4 or IPv6.
  2. Parse the IP header to extract the protocol field (TCP = 6, UDP = 17, ICMP = 1).
  3. Parse the transport header (TCP or UDP) to read the destination port.
  4. Look up the port and protocol in a BPF map that holds the active rules.
  5. Return XDP_DROP if a block rule matches, or XDP_PASS to let the packet continue into the kernel stack.

Because the XDP program is stateless, it cannot track TCP connections or count per-flow byte rates. However, it can count packets per port using BPF maps and enforce a packet-per-second rate limit by storing timestamps and packet counters in a hash map keyed by destination port or source IP.

Single XDP Program, Multiple Rules via BPF Maps

The Linux kernel allows only one XDP program per network interface. To support dozens or hundreds of port-block rules without recompiling, the program reads its configuration from one or more BPF maps—kernel data structures accessible from both eBPF code and userspace. A typical rule map might look like:

struct rule_key {
    __u16 port;
    __u8  proto;
};

struct rule_value {
    __u8  action;      // 0 = pass, 1 = drop, 2 = rate_limit
    __u32 max_pps;     // packets per second threshold
    __u64 last_time;   // nanosecond timestamp
    __u64 packet_count;
};

struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __type(key, struct rule_key);
    __type(value, struct rule_value);
    __uint(max_entries, 256);
} pakkt_rules SEC(".maps");

Userspace tools—whether a custom Go agent or bpftool—update this map to add, modify, or delete rules without reloading the XDP program. This design is central to platforms like PAKKT.io, which allow administrators to manage up to 256 simultaneous XDP rules through a web dashboard, all driven by a single pre-compiled XDP program per interface.

Example: Blocking UDP Port 19132 (Minecraft Bedrock Query)

Port 19132/UDP is often abused in reflection attacks. To block it at XDP, you would insert a rule into the map:

# Using bpftool (manual example)
bpftool map update name pakkt_rules \
  key hex 32 4a 11 00 \
  value hex 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

# Key breakdown (little-endian):
#   port = 19132 (0x4a4c, bytes 32 4a in LE u16)
#   proto = 17 (0x11, UDP)

# Value breakdown:
#   action = 1 (drop)
#   max_pps, timestamps = 0 (unused for static drop)

From that moment, every UDP packet arriving on the protected interface with destination port 19132 will return XDP_DROP and never enter the kernel network stack. The CPU cost per dropped packet is measured in nanoseconds, and the server can sustain multi-gigabit floods on that port without degradation.



XDP vs. nftables: When to Use Each for Port Blocking

While XDP excels at stateless, high-speed packet filtering, nftables remains essential for stateful rules, connection tracking, and complex logic that requires seeing the full packet context. The two technologies are complementary, not competitors.

Criterion XDP (eBPF) nftables
Packet processing stage Driver layer, before skb allocation Netfilter hooks (prerouting, input, forward, output, postrouting)
State tracking Stateless (can simulate via BPF maps) Full conntrack (ct state new, established, related, invalid)
Per-packet cost Sub-microsecond Low-microsecond range (depends on ruleset size)
Max throughput (drops) Several million pps per core Hundreds of thousands pps per core
TCP flag inspection Manual parsing in C Native (tcp flags syn, tcp flags & (fin|rst) != 0)
Rate-limit granularity Packet-per-second only Packet-per-second and byte-per-second (limit rate, limit rate over)
Integration with Docker, fail2ban Transparent (different layer) Potential conflict unless isolated in separate table

Recommended Dual-Layer Strategy

For production servers, the best practice in 2026 is to combine both:

  • XDP layer: Block known-bad ports, enforce global packet-per-second caps, drop malformed packets, and handle the bulk of volumetric floods.
  • nftables layer: Allow only ct state established,related traffic, enforce SYN rate-limits, block invalid TCP flags, and apply per-connection byte-rate quotas.

This architecture ensures that the heaviest, least sophisticated attacks never reach the stateful firewall, preserving conntrack capacity and CPU for legitimate connection handling. PAKKT.io deploys exactly this model: a single XDP program per interface (the PAKKT Engine) backed by BPF maps, plus a complementary inet pakkt nftables table that coexists cleanly with Docker's nat table, fail2ban's dynamic sets, and any legacy iptables-persistent rules.

Example nftables Rule to Complement XDP

Once XDP has dropped the flood traffic, nftables can enforce stateful acceptance on a game server port:

nft add table inet pakkt
nft add chain inet pakkt input { type filter hook input priority 0 \; policy accept \; }

# Accept established connections (XDP passed them, now track state)
nft add rule inet pakkt input ct state established,related accept

# Rate-limit NEW connections to Minecraft Java (25565/TCP)
nft add rule inet pakkt input tcp dport 25565 ct state new limit rate 100/second accept

# Drop invalid or untracked packets that somehow bypassed XDP
nft add rule inet pakkt input ct state invalid drop

This rule will never see the millions of spoofed SYN packets that XDP already dropped; it only processes the much smaller stream of packets that passed the XDP filter, keeping CPU usage and conntrack entries low.



Deploying and Managing XDP Port Blocks at Scale

Manually compiling, loading, and updating XDP programs is feasible for a single server or a proof-of-concept, but impractical for fleets of game servers, API gateways, or edge nodes. In production, you need:

  • Centralized rule management (add/remove ports from a dashboard)
  • Real-time observability (packets dropped per port, top attacker IPs, geographic distribution)
  • Automated agent deployment, updates, and health monitoring
  • Audit logs and compliance records

PAKKT.io was built to solve this operational challenge. A lightweight Go agent (under 5 MB RAM, less than 1 % CPU) runs on each Linux server, maintains an mTLS-secured WebSocket to the central panel, and applies rule changes by updating BPF maps in real time. The panel provides:

  • A world map with GeoIP visualization of attack sources
  • Per-port and per-rule metrics stored in TimescaleDB
  • Top source IPs and top countries analytics
  • Dual-layer IP blacklist/whitelist synchronized across XDP and nftables
  • Template marketplace for sharing and reusing common rulesets (Minecraft, FiveM, Rust, generic UDP services)
  • Integrations with Pterodactyl v1.x, Pelican, and WHMCS (via public API)

The agent automatically reloads the XDP program and nftables ruleset on boot, self-updates via SHA256-verified binaries, and sends a 30-second heartbeat to detect crashes or connectivity loss. For teams managing dozens or hundreds of nodes, this level of automation is the difference between reactive firefighting and proactive defense.

Why Not Build Your Own?

Writing production-grade XDP code requires deep knowledge of eBPF verifier constraints, correct packet parsing (handling VLAN tags, IPv6 extension headers, fragmentation), map synchronization, and safe concurrent updates from userspace. A bug in pointer arithmetic or a missing bounds check will cause the verifier to reject your program—or worse, allow it to load and crash the kernel under specific traffic conditions. Maintaining this codebase, keeping it compatible with kernel versions 5.x through 6.x, and integrating it with a web dashboard, metrics pipeline, and API is a multi-month engineering project.

For the cost of €3 per agent per month (with a 7-day free trial on the first agent), PAKKT delivers a battle-tested, continuously updated XDP+nftables stack with zero upfront development. You can deploy protection in under 30 seconds and focus your engineering resources on your core product, not on low-level kernel programming.



Conclusion

Blocking a port with an XDP rule places your defense at the earliest possible interception point, dropping unwanted traffic before it can consume kernel resources or fill conntrack tables. By combining a stateless XDP layer for volumetric filtering with a stateful nftables layer for connection tracking and complex logic, you build a resilient, high-performance firewall capable of withstanding modern DDoS attacks. In 2026, this dual-layer architecture—orchestrated through centralized management and real-time observability—has become the standard for protecting exposed Linux servers at scale.



FAQ

Can I block multiple port ranges in a single XDP rule?

XDP itself does not have a native "range" primitive; you must implement range checking in the eBPF C code by comparing the destination port against min and max values. Alternatively, insert one map entry per port in the range, though this consumes more map slots. PAKKT's engine supports configurable port ranges by encoding min_port and max_port in the rule structure and performing the comparison in the XDP program before map lookup.

Will blocking a port at XDP interfere with Docker or Kubernetes networking?

No. XDP operates at the physical interface driver layer, before the kernel assigns packets to network namespaces or bridges. Docker and Kubernetes create virtual interfaces (veth pairs, bridge, overlay networks) that only see packets the XDP program has allowed to pass. As long as your XDP rules permit legitimate traffic to reach the container's published ports, containerized applications will function normally. The nftables component should be isolated in a separate table (e.g., inet pakkt) to avoid conflicting with Docker's nat and filter chains.

How do I measure the performance improvement of XDP over nftables for port blocking?

Use a traffic generator like pktgen (kernel module) or TRex to send a controlled flood of packets to a blocked port. Measure CPU usage (via mpstat or perf) and dropped packet rate (via ethtool -S for XDP, nft list ruleset counters for nftables). In typical benchmarks, XDP sustains several million packets per second per core with under 10 % CPU, while nftables begins to saturate a core at a few hundred thousand pps. The exact numbers depend on packet size, rule complexity, and hardware offload capabilities (e.g., native XDP on Mellanox or Intel NICs).

Protect your servers

Deploy PAKKT in 30 seconds

Dual-layer kernel protection. XDP + nftables. Driven from a central panel. 7-day free trial.