Tools: Update: Dropping 100Gbps DDoS Attacks: The Ultimate eBPF & XDP Guide

Tools: Update: Dropping 100Gbps DDoS Attacks: The Ultimate eBPF & XDP Guide

The Kernel Bypass Revolution

The Enterprise Mitigation Pipeline

The BGP Anycast & Null-Route Reality

Writing a Production-Ready XDP Program

Compile and Attach

Real-Time Observability

Choosing the Right Infrastructure When a massive volumetric attack hits your server, deploying iptables, ufw, or fail2ban is an exercise in futility. In the traditional Linux networking stack, by the time a packet reaches netfilter, the kernel has already allocated an sk_buff (socket buffer) memory structure and executed context switches. If 20 million malicious UDP packets arrive per second, the sheer overhead of allocating and destroying those structures will result in 100% CPU starvation. Your server goes down before your application even sees the traffic. XDP (eXpress Data Path) attaches an eBPF program directly to the Network Interface Card (NIC) driver. Before the kernel even realizes a packet exists, your XDP code executes. An xdp_drop instruction discards the packet instantly with virtually zero CPU overhead. A common misconception is that XDP is a magic bullet for all security threats. In reality, XDP executes statelessly (though it maintains limited state via BPF maps). It cannot perform full connection tracking or inspect HTTP headers inside TLS tunnels. To build a robust defense, XDP must act as the initial L3/L4 shield within a broader pipeline: Architect's Reality Check: The Upstream Blackhole

Many tutorials run XDP on a 1Gbps Cloud VM and show beautiful Flame Graphs proving CPU usage remains low. This is a fatal illusion. XDP saves your CPU, but it does not save your bandwidth. If a 40Gbps flood hits your 1Gbps VM, the pipe saturates instantly. Worse, the upstream ISP will panic and issue a Null-Route (Blackhole) to your IP, completely isolating your server from the internet. To effectively mitigate enterprise attacks, your infrastructure must support BGP FlowSpec and Anycast Routing to distribute the attack load across global datacenters. Furthermore, you need 100Gbps unmetered uplinks to physically absorb the raw volume so your eBPF program can silently scrub the traffic locally. Writing toy scripts is easy, but wire-speed production code must handle memory exhaustion and multi-queue architectures. At 100Gbps, NICs distribute packets across multiple CPU cores. A standard BPF_MAP_TYPE_HASH will cause severe lock contention and race conditions. Protecting Against Map ExhaustionAttackers spoof source IPs to fill your BPF maps, causing memory allocation failures. We mitigate this using BPF_MAP_TYPE_LRU_PERCPU_HASH. The 'Per-CPU' aspect solves race conditions, while the 'LRU' (Least Recently Used) automatically evicts old spoofed IPs to prevent DoS via map exhaustion. Architect's Check: The BPF Verifier

The Linux kernel utilizes an in-kernel engine called the eBPF Verifier. It analyzes your bytecode before it runs to ensure it won't crash the kernel. If your code exceeds the strict 512-byte stack limit, uses unbounded loops, or fails to implement strict bounds checking (like the data_end checks above), the verifier will reject the program at load time. Compile the C code into an ELF object and attach it using the iproute2 toolkit. (Always benchmark using tools like pktgen or trex to verify Packets Per Second (PPS) capacity before moving to production). Dropping packets is only half the battle. Without metrics, your mitigation is a black box. Because we added a drop_stats PERCPU map, your SOC team can visualize the scrubbing efficiency directly from the kernel. In a production environment, you should run a user-space Go or Python daemon that continuously reads this BPF map and pipes the data into a Prometheus Exporter to build real-time Grafana dashboards. How should you deploy your mitigation strategy? Here is the architectural reality: For organizations ready to build their own unmetered scrubbing centers, ServerMO provides the ultimate foundation. Our 10Gbps to 100Gbps Dedicated Bare Metal Servers feature enterprise-grade AMD EPYC/Intel CPUs, BGP integration, and Mellanox SmartNICs natively optimized for Native and Offloaded XDP. Stop paying the Cloudflare tax. Deploy raw power. Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Code Block

Copy

[Internet] ↓ [XDP Drop] → (Drops Volumetric L3/L4 Attacks: SYN floods, UDP floods, Amplification) ↓ [iptables / nftables] → (Stateful firewalling for surviving packets) ↓ [Reverse Proxy (Nginx)] → (TLS Termination & Connection Management) ↓ [WAF] → (Layer 7 Defense: SQLi, XSS, HTTP Floods) ↓ [Application] [Internet] ↓ [XDP Drop] → (Drops Volumetric L3/L4 Attacks: SYN floods, UDP floods, Amplification) ↓ [iptables / nftables] → (Stateful firewalling for surviving packets) ↓ [Reverse Proxy (Nginx)] → (TLS Termination & Connection Management) ↓ [WAF] → (Layer 7 Defense: SQLi, XSS, HTTP Floods) ↓ [Application] [Internet] ↓ [XDP Drop] → (Drops Volumetric L3/L4 Attacks: SYN floods, UDP floods, Amplification) ↓ [iptables / nftables] → (Stateful firewalling for surviving packets) ↓ [Reverse Proxy (Nginx)] → (TLS Termination & Connection Management) ↓ [WAF] → (Layer 7 Defense: SQLi, XSS, HTTP Floods) ↓ [Application] #include <linux/bpf.h> #include <linux/if_ether.h> #include <linux/ip.h> #include <linux/tcp.h> #include <bpf/bpf_helpers.h> #define MAX_ENTRIES 10000000 #define SYN_RATE_LIMIT 200 struct rate_limit_entry { __u64 last_update; __u32 count; }; // 1. LRU Per-CPU Hash to prevent Map DoS and Race Conditions struct { __uint(type, BPF_MAP_TYPE_LRU_PERCPU_HASH); __uint(max_entries, MAX_ENTRIES); __type(key, __u32); __type(value, struct rate_limit_entry); } rate_limit_map SEC(".maps"); // 2. Statistics Map for Observability struct { __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); __uint(max_entries, 2); // index 0: pass, index 1: drop __type(key, __u32); __type(value, __u64); } drop_stats SEC(".maps"); static __always_inline void increment_stat(__u32 index) { __u64 *value = bpf_map_lookup_elem(&drop_stats, &index); if (value) *value += 1; } SEC("xdp") int xdp_syn_flood_protect(struct xdp_md *ctx) { void *data_end = (void *)(long)ctx->data_end; void *data = (void *)(long)ctx->data; struct ethhdr *eth = data; if ((void *)(eth + 1) > data_end) return XDP_PASS; if (eth->h_proto != __constant_htons(ETH_P_IP)) return XDP_PASS; struct iphdr *iph = (void *)(eth + 1); if ((void *)(iph + 1) > data_end) return XDP_PASS; if (iph->protocol != IPPROTO_TCP) return XDP_PASS; // 3. Robust TCP parsing (Handling IP Options) if (iph->ihl < 5) return XDP_PASS; struct tcphdr *tcph = (void *)iph + (iph->ihl * 4); if ((void *)(tcph + 1) > data_end) return XDP_PASS; if (!(tcph->syn && !tcph->ack)) { increment_stat(0); return XDP_PASS; } // src_ip is in network byte order __u32 src_ip = iph->saddr; __u64 now = bpf_ktime_get_ns(); struct rate_limit_entry *entry = bpf_map_lookup_elem(&rate_limit_map, &src_ip); if (entry) { if (now - entry->last_update < 1000000000ULL) { entry->count++; if (entry->count > SYN_RATE_LIMIT) { increment_stat(1); // Record dropped packet return XDP_DROP; } } else { entry->last_update = now; entry->count = 1; } } else { struct rate_limit_entry new_entry = { .last_update = now, .count = 1 }; bpf_map_update_elem(&rate_limit_map, &src_ip, &new_entry, BPF_ANY); } increment_stat(0); return XDP_PASS; } char _license[] SEC("license") = "GPL"; #include <linux/bpf.h> #include <linux/if_ether.h> #include <linux/ip.h> #include <linux/tcp.h> #include <bpf/bpf_helpers.h> #define MAX_ENTRIES 10000000 #define SYN_RATE_LIMIT 200 struct rate_limit_entry { __u64 last_update; __u32 count; }; // 1. LRU Per-CPU Hash to prevent Map DoS and Race Conditions struct { __uint(type, BPF_MAP_TYPE_LRU_PERCPU_HASH); __uint(max_entries, MAX_ENTRIES); __type(key, __u32); __type(value, struct rate_limit_entry); } rate_limit_map SEC(".maps"); // 2. Statistics Map for Observability struct { __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); __uint(max_entries, 2); // index 0: pass, index 1: drop __type(key, __u32); __type(value, __u64); } drop_stats SEC(".maps"); static __always_inline void increment_stat(__u32 index) { __u64 *value = bpf_map_lookup_elem(&drop_stats, &index); if (value) *value += 1; } SEC("xdp") int xdp_syn_flood_protect(struct xdp_md *ctx) { void *data_end = (void *)(long)ctx->data_end; void *data = (void *)(long)ctx->data; struct ethhdr *eth = data; if ((void *)(eth + 1) > data_end) return XDP_PASS; if (eth->h_proto != __constant_htons(ETH_P_IP)) return XDP_PASS; struct iphdr *iph = (void *)(eth + 1); if ((void *)(iph + 1) > data_end) return XDP_PASS; if (iph->protocol != IPPROTO_TCP) return XDP_PASS; // 3. Robust TCP parsing (Handling IP Options) if (iph->ihl < 5) return XDP_PASS; struct tcphdr *tcph = (void *)iph + (iph->ihl * 4); if ((void *)(tcph + 1) > data_end) return XDP_PASS; if (!(tcph->syn && !tcph->ack)) { increment_stat(0); return XDP_PASS; } // src_ip is in network byte order __u32 src_ip = iph->saddr; __u64 now = bpf_ktime_get_ns(); struct rate_limit_entry *entry = bpf_map_lookup_elem(&rate_limit_map, &src_ip); if (entry) { if (now - entry->last_update < 1000000000ULL) { entry->count++; if (entry->count > SYN_RATE_LIMIT) { increment_stat(1); // Record dropped packet return XDP_DROP; } } else { entry->last_update = now; entry->count = 1; } } else { struct rate_limit_entry new_entry = { .last_update = now, .count = 1 }; bpf_map_update_elem(&rate_limit_map, &src_ip, &new_entry, BPF_ANY); } increment_stat(0); return XDP_PASS; } char _license[] SEC("license") = "GPL"; #include <linux/bpf.h> #include <linux/if_ether.h> #include <linux/ip.h> #include <linux/tcp.h> #include <bpf/bpf_helpers.h> #define MAX_ENTRIES 10000000 #define SYN_RATE_LIMIT 200 struct rate_limit_entry { __u64 last_update; __u32 count; }; // 1. LRU Per-CPU Hash to prevent Map DoS and Race Conditions struct { __uint(type, BPF_MAP_TYPE_LRU_PERCPU_HASH); __uint(max_entries, MAX_ENTRIES); __type(key, __u32); __type(value, struct rate_limit_entry); } rate_limit_map SEC(".maps"); // 2. Statistics Map for Observability struct { __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); __uint(max_entries, 2); // index 0: pass, index 1: drop __type(key, __u32); __type(value, __u64); } drop_stats SEC(".maps"); static __always_inline void increment_stat(__u32 index) { __u64 *value = bpf_map_lookup_elem(&drop_stats, &index); if (value) *value += 1; } SEC("xdp") int xdp_syn_flood_protect(struct xdp_md *ctx) { void *data_end = (void *)(long)ctx->data_end; void *data = (void *)(long)ctx->data; struct ethhdr *eth = data; if ((void *)(eth + 1) > data_end) return XDP_PASS; if (eth->h_proto != __constant_htons(ETH_P_IP)) return XDP_PASS; struct iphdr *iph = (void *)(eth + 1); if ((void *)(iph + 1) > data_end) return XDP_PASS; if (iph->protocol != IPPROTO_TCP) return XDP_PASS; // 3. Robust TCP parsing (Handling IP Options) if (iph->ihl < 5) return XDP_PASS; struct tcphdr *tcph = (void *)iph + (iph->ihl * 4); if ((void *)(tcph + 1) > data_end) return XDP_PASS; if (!(tcph->syn && !tcph->ack)) { increment_stat(0); return XDP_PASS; } // src_ip is in network byte order __u32 src_ip = iph->saddr; __u64 now = bpf_ktime_get_ns(); struct rate_limit_entry *entry = bpf_map_lookup_elem(&rate_limit_map, &src_ip); if (entry) { if (now - entry->last_update < 1000000000ULL) { entry->count++; if (entry->count > SYN_RATE_LIMIT) { increment_stat(1); // Record dropped packet return XDP_DROP; } } else { entry->last_update = now; entry->count = 1; } } else { struct rate_limit_entry new_entry = { .last_update = now, .count = 1 }; bpf_map_update_elem(&rate_limit_map, &src_ip, &new_entry, BPF_ANY); } increment_stat(0); return XDP_PASS; } char _license[] SEC("license") = "GPL"; # Compile the program clang -O2 -g -target bpf -c xdp_syn_flood.c -o xdp_syn_flood.o # Attach to your Mellanox NIC in Native mode (xdpdrv) sudo ip link set dev enp3s0 xdpdrv obj xdp_syn_flood.o sec xdp # Compile the program clang -O2 -g -target bpf -c xdp_syn_flood.c -o xdp_syn_flood.o # Attach to your Mellanox NIC in Native mode (xdpdrv) sudo ip link set dev enp3s0 xdpdrv obj xdp_syn_flood.o sec xdp # Compile the program clang -O2 -g -target bpf -c xdp_syn_flood.c -o xdp_syn_flood.o # Attach to your Mellanox NIC in Native mode (xdpdrv) sudo ip link set dev enp3s0 xdpdrv obj xdp_syn_flood.o sec xdp # Dump the statistics map directly from the kernel sudo bpftool map dump name drop_stats # Dump the statistics map directly from the kernel sudo bpftool map dump name drop_stats # Dump the statistics map directly from the kernel sudo bpftool map dump name drop_stats