Auto XDP leverages eBPF technology to provide a lightweight and high-performance anti-DDoS solution for personal cloud instances. With automatic port whitelisting, this tool alleviates the burden of manual firewall management while ensuring fast packet processing at the NIC driver level. Ideal for users seeking efficiency and enhanced security.
A lightweight XDP/eBPF firewall for automatic port whitelisting and basic DDoS protection on Linux hosts.
Although there are some XDP firewall solutions available, Basic XDP provides users with automatic port whitelisting, which makes maintenance easier.
⚠️ XDP only filters traffic that reaches your NIC. If your upstream bandwidth is already saturated by a volumetric attack, this tool cannot help. For large-scale DDoS mitigation, consider upstream scrubbing services or a DDoS-protected hosting provider.
XDP (eXpress Data Path) is an eBPF-based, high-performance packet processing path that runs before packets enter the Linux networking stack (at the NIC driver level). This makes it significantly faster than traditional iptables/nftables filtering.
Personal cloud instances are constantly scanned and probed. Traditional firewalls like iptables work, but they process packets after the kernel networking stack — adding latency and CPU overhead.
Basic XDP hooks in at the NIC driver level, before any kernel processing. And unlike other XDP solutions, it manages the port whitelist for you: a daemon watches which ports are actually open on your system and keeps the active backend in sync automatically. When a host cannot run XDP, it can fall back to an nftables ruleset instead of failing outright.
Incoming Packet
│
▼
┌─────────────┐
│ NIC Driver │ ← XDP hooks here (before kernel stack)
└──────┬──────┘
│
▼
┌──────────────────────────────┐
│ xdp_port_whitelist │
│ │
│ ETH → IPv4/IPv6 → TCP/UDP │
│ │
│ IPv4 TCP SYN? → whitelist + │
│ conntrack │
│ IPv4 TCP ACK? → conntrack │
│ IPv4 UDP? → ct/port/IP │
│ ICMP/ARP? → PASS │
│ │
│ Not in whitelist → DROP │
└──────────────────────────────┘
│
▼
XDP_PASS / XDP_DROP
xdp_firewall.c — eBPF/XDP kernel program that filters packets at wire speedtc_flow_track.c — eBPF tc egress helper that records outbound IPv4/IPv6 TCP SYN packets and UDP reply tuplesxdp_port_sync.py — userspace daemon that syncs TCP/UDP listening ports and trusted IPv4 source IPsbxdp — operator CLI for statistics, sync, service control, and daemon log levelsetup_xdp.sh — installer that compiles the BPF objects, installs the runtime launcher, and sets up boot-time auto-synctc egress program records host-initiated IPv4/IPv6 TCP SYN packets and UDP reply tuples so return traffic can be matched at XDP without reopening the old bypassesudp_whitelist, reply traffic can be matched by udp_conntrack, and explicitly trusted IPv4 sources can still be allowed via trusted_src_ipsudp_conntrack, and inbound server traffic still uses udp_whitelist (note: trusted_src_ips is currently IPv4-only)tcp_conntrack, which helps preserve active sessions after re-attaching XDP or manual map clears.bxdp log-level debug|info|warning|error updates the installed service config and restarts itnftables rulesetnftables support is used automatically as the compatibility fallback when XDP cannot be attachedclang, llvm — compile BPFlibbpf or libbpf-dev / libbpf-devel — BPF headers, depending on distrobpftool — manage BPF mapsiproute2 or iproute — provides both ip and tc for XDP attach and UDP egress trackingpython3 — sync daemon runtimenftables — compatibility fallback backendcurl -fsSL https://raw.githubusercontent.com/Kookiejarz/basic_xdp/refs/heads/main/setup_xdp.sh | sudo bash
curl -fsSL https://raw.githubusercontent.com/Kookiejarz/basic_xdp/refs/tags/v26.4.7a/setup_xdp.sh | sudo bash
Using a tag gives you a reproducible installer version instead of tracking the latest main branch.
When the installer is executed from stdin (curl | bash), it prefers the matching GitHub source files instead of stale local files from the current working directory.
The repository includes a GitHub Actions matrix that installs basic runtime tools in supported Linux container images and runs a non-destructive setup_xdp.sh --dry-run smoke test.
The installer reads /etc/os-release to classify the distro family before choosing the matching package-manager and dependency set.
You can also run the non-destructive smoke test locally:
bash setup_xdp.sh --dry-run
If you only want the package-manager and init-system probe, use:
bash setup_xdp.sh --check-env
git clone https://github.com/Kookiejarz/basic_xdp.git
cd basic_xdp
# Auto-detect interface
sudo bash setup_xdp.sh
# Or specify interface
sudo bash setup_xdp.sh eth0
# Compare local files with GitHub first, then decide interactively
sudo bash setup_xdp.sh --check-update
# Non-interactive mode for CI / automation
sudo bash setup_xdp.sh --check-update --force
xdp_firewall.c / tc_flow_track.c / xdp_port_sync.py / bxdp by default from a local checkout; when run from stdin, it prefers the matching GitHub copiestcp_conntrack before attaching XDPtc clsact egress program that records outbound TCP SYN and UDP reply tuplesnftables automatically if XDP cannot be attached/usr/local/bin/basic_xdp_start.sh/usr/local/bin/xdp_port_sync.pyxdp-port-sync on systemd or OpenRC when availablePinned directory: /sys/fs/bpf/xdp_fw/
| Map | Type | Max Entries | Key | Value |
|---|---|---|---|---|
tcp_whitelist | ARRAY | 65536 | __u32 port (host byte order) | __u32 (1 = allow) |
udp_whitelist | ARRAY | 65536 | __u32 port (host byte order) | __u32 (1 = allow) |
tcp_conntrack | LRU_HASH | 65536 | struct ct_key { family, sport, dport, saddr[4], daddr[4] } | __u64 ktime_ns |
udp_conntrack | LRU_HASH | 65536 | struct ct_key { family, sport, dport, saddr[4], daddr[4] } | __u64 ktime_ns |
trusted_src_ips | HASH | 256 | __be32 IPv4 source address | __u32 (1 = trusted) |
pkt_counters | PERCPU_ARRAY | 10 | __u32 counter index | __u64 packet count |
# Allow TCP port 8080
bpftool map update pinned /sys/fs/bpf/xdp_fw/tcp_whitelist \
key 0x90 0x1f 0x00 0x00 value 0x01 0x00 0x00 0x00
# Remove TCP port 8080
bpftool map delete pinned /sys/fs/bpf/xdp_fw/tcp_whitelist \
key 0x90 0x1f 0x00 0x00
# View current TCP whitelist
bpftool map dump pinned /sys/fs/bpf/xdp_fw/tcp_whitelist
Key encoding note: the map type is now ARRAY (BPF_MAP_TYPE_ARRAY), so the key is a 4-byte little-endian __u32 port number (host byte order). Example: 8080 = 0x00001F90 → bytes 0x90 0x1f 0x00 0x00
Originally, this project used BPF_MAP_TYPE_HASH for the whitelist. We transitioned to BPF_MAP_TYPE_ARRAY for several critical reasons:
The daemon xdp_port_sync.py runs behind the launcher /usr/local/bin/basic_xdp_start.sh and provides real-time updates for either backend:
exec() and exit() events immediately.psutil to read /proc directly for listening ports (no slow ss or netstat subprocesses).nftables sets, depending on what the host supports.LISTEN state, the daemon syncs unconnected bound UDP sockets (no remote peer) into udp_whitelist, which is a practical approximation of server-style UDP ports.trusted_src_ips map for reply-style UDP traffic such as DNS or NTP.auto mode, the daemon only selects XDP when the required pinned maps are present; otherwise it falls back to nftables instead of crashing.Outbound TCP/UDP reply tracking is kernel-side: a tc egress program records reverse reply tuples into tcp_conntrack and udp_conntrack, and the XDP ingress path checks those maps before falling back to tcp_whitelist, udp_whitelist, or trusted_src_ips.
Edit xdp_port_sync.py to always allow specific ports:
TCP_PERMANENT = {22: "SSH-fallback"} # Optional: add ports you never want blocked
UDP_PERMANENT = {50000: "custom-udp-service"} # Use this for real high-port UDP services
TRUSTED_SRC_IPS = {"1.1.1.1": "cloudflare-dns"}
If a real UDP server uses a high port, add it to UDP_PERMANENT explicitly so it remains whitelisted even when the daemon's socket heuristics cannot distinguish it cleanly from transient client traffic.
You can also add trusted IPv4 sources at runtime:
python3 /usr/local/bin/xdp_port_sync.py --backend auto --trusted-ip 1.1.1.1 cloudflare-dns
python3 /usr/local/bin/xdp_port_sync.py --backend auto --trusted-ip 129.6.15.28 ntp-nist --dry-run
--trusted-ip currently affects the XDP backend. The nftables fallback continues to use its own compatibility ruleset.
# systemd
systemctl status xdp-port-sync
journalctl -u xdp-port-sync -f
# OpenRC
rc-service xdp-port-sync status
# Manual foreground run
/usr/local/bin/basic_xdp_start.sh
# One-shot sync with automatic backend selection
python3 /usr/local/bin/xdp_port_sync.py --backend auto
python3 /usr/local/bin/xdp_port_sync.py --backend auto --dry-run
# Increase foreground verbosity temporarily
python3 /usr/local/bin/xdp_port_sync.py --backend auto --log-level debug
Basic XDP installs a convenience command /usr/local/bin/bxdp. Statistics are now built directly into bxdp, so you only need one operational command after installation.
# Single snapshot
sudo bxdp
# Real-time refresh
sudo bxdp watch
# Show delta rates (pps / bps)
sudo bxdp stats --rates
# Combine both
sudo bxdp stats --watch --rates --interval 2
# Run one manual sync
sudo bxdp sync
# View or change daemon log level
sudo bxdp log-level
sudo bxdp log-level debug
# Service control
sudo bxdp start
sudo bxdp stop
sudo bxdp restart
sudo bxdp status
What it shows:
xdp backend: per-category packet counters from /sys/fs/bpf/xdp_fw/pkt_counters, plus interface RX totalsnftables backend: current drop counter from the inet basic_xdp input chain, plus interface RX totals--rates: packet deltas for XDP counters, and packet/bit deltas where byte counters are availableCounter labels in bxdp are intentionally human-readable:
TCP_NEW_ALLOW counts pure SYN packets admitted by tcp_whitelistTCP_ESTABLISHED counts TCP packets admitted by tcp_conntrackTCP_CT_MISS counts TCP ACK packets dropped because no conntrack entry existedIPv6_ICMP covers ICMPv6, neighbor discovery, and other non-TCP/UDP IPv6 traffic that is passed throughARP_NON_IP covers ARP and other non-IP Ethernet trafficAfter installation, these are the main commands you will actually use:
# Help
sudo bxdp help
# Current statistics snapshot
sudo bxdp
# Live statistics
sudo bxdp watch
# Delta rates
sudo bxdp stats --rates
# Live delta rates
sudo bxdp stats --watch --rates --interval 2
# Run one manual sync
sudo bxdp sync
# Change daemon log verbosity and restart the service
sudo bxdp log-level
sudo bxdp log-level debug
sudo bxdp log-level info
# Service control
sudo bxdp start
sudo bxdp stop
sudo bxdp status
sudo bxdp restart
When you run the installer from a cloned repo, local source files win by default. If you want the script to compare your local copies with GitHub first, use:
sudo bash setup_xdp.sh --check-update
In --check-update mode, the installer:
xdp_firewall.c, tc_flow_track.c, xdp_port_sync.py, and bxdp to temporary filesFor CI or automated deployment, use:
sudo bash setup_xdp.sh --force
Or combine it with source comparison:
sudo bash setup_xdp.sh --check-update --force
In --force mode, the installer skips confirmation prompts and:
--check-update finds a hash mismatchtcp_whitelist → insert flow key into tcp_conntrack and PASStcp_conntrack → PASSCNT_TCP_CT_MISS and DROPtc egress program records host-initiated IPv4/IPv6 TCP SYN packets immediately, closing the race where a very short outbound connection could receive SYN-ACK before conntrack state existed.setup_xdp.sh pre-seeds existing IPv4/IPv6 TCP sessions into tcp_conntrack before re-attaching XDP, which helps preserve active sessions during reinstall/restart.udp_conntrack → PASSudp_whitelist → PASStrusted_src_ips → PASSudp_conntrack → PASSudp_whitelist → PASSTraverses IPv6 extension headers up to 6 levels deep to locate the transport protocol and prevent crafted-header bypass attacks. This logic now exists on both the XDP ingress path and the tc egress tracker, so IPv6 reply-state tracking is not limited to the simplest nexthdr cases. Non-initial IPv6 fragments are explicitly counted and dropped before the transport parser, so they cannot slip through on a failed bounds check.
# Detach XDP if it is attached
ip link set dev eth0 xdp off
# Remove the TCP/UDP reply tracker
tc filter del dev eth0 egress pref 49152 2>/dev/null || true
# Remove pinned maps and nftables fallback table
rm -rf /sys/fs/bpf/xdp_fw
nft delete table inet basic_xdp 2>/dev/null || true
# systemd
systemctl disable --now xdp-port-sync 2>/dev/null || true
rm /etc/systemd/system/xdp-port-sync.service
systemctl daemon-reload 2>/dev/null || true
# OpenRC
rc-service xdp-port-sync stop 2>/dev/null || true
rc-update del xdp-port-sync default 2>/dev/null || true
rm /etc/init.d/xdp-port-sync
# Remove installed runtime files
rm /usr/local/bin/xdp_port_sync.py
rm /usr/local/bin/bxdp
rm /usr/local/bin/basic_xdp_start.sh
rm -rf /usr/local/lib/basic_xdp
rm -rf /etc/basic_xdp
This benchmark simulates a volumetric UDP flood attack. We used a high-performance AMD EPYC™ 7Y43 server as the "Attacker" to stress-test a 1 vCPU AMD Ryzen 9 3900X instance protected by Basic XDP.
pktgen (Linux Kernel Packet Generator)| Metric | Basic XDP OFF | Basic XDP ON | Improvement |
|---|---|---|---|
| Softirq (si) CPU Usage | 85.9% | 3.0% | ~28x Reduction |
| System Responsiveness | Extremely Laggy | Smooth | Significant |
| Packet Handling | Processed by Kernel Stack | Dropped at Driver Level | - |
When XDP is off, the kernel networking stack processes every incoming packet, consuming nearly all CPU via soft interrupts. With XDP on, packets are dropped at the NIC driver level before reaching the stack — the same 367k PPS flood only uses 3% CPU, and the machine stays fully responsive.
XDP OFF — softirq at 85.9% under flood:

XDP ON — same flood, CPU drops to 3.0%:

XDP ON — before attack:

XDP ON — after attack:

# Load the kernel module
modprobe pktgen
# Configure the device (replace enp3s0 with your interface name)
PGDEV=/proc/net/pktgen/INTERFACE
echo "rem_device_all" > /proc/net/pktgen/kpktgend_0
echo "add_device INTERFACE" > /proc/net/pktgen/kpktgend_0
# Set attack parameters
echo "count 10000000" > $PGDEV # Send 10 million packets
echo "pkt_size 64" > $PGDEV # Small packets put more stress on the CPU
echo "dst TARGET_IP" > $PGDEV # Target IP
echo "dst_mac TARGET_MAC" > $PGDEV # Target MAC
echo "clone_skb 100" > $PGDEV # Speed up packet generation
Contributions are welcome! If you have a bug fix, performance improvement, or new feature in mind:
git checkout -b feature/my-improvement)MIT © 2026 Yunheng Liu