How to Build a Custom DDoS Scrubbing Gateway with eBPF & FRR

Transform a high-bandwidth dedicated server into a high-performance DDoS mitigation node.

Build a Custom DDoS Scrubbing Gateway with eBPF & FRR

Volumetric Distributed Denial of Service (DDoS) attacks can overwhelm even the most robust infrastructure. When under attack, many administrators try to block the malicious traffic using standard firewalls like iptables, UFW, or nftables. However, during a massive volumetric flood (like DNS or NTP amplification), the server's CPU quickly reaches 100% exhaustion just trying to process and drop the packets, taking the server offline anyway.

To survive high-capacity attacks, enterprise networks use dedicated "scrubbing centers." In this tutorial, you will learn how to build your own scrubbing gateway using a high-bandwidth bare metal server from EPY Host. By combining FRRouting (FRR) to steer traffic and XDP/eBPF to drop malicious packets directly at the network card driver level, you can process millions of packets per second without breaking a sweat.

What You'll Learn

The Reality of Traditional Firewall Bottlenecks

When a packet reaches a Linux server, the Network Interface Card (NIC) hardware receives it and triggers an interrupt. The Linux kernel then allocates a data structure called an sk_buff (socket buffer) to manage the packet as it travels up the network stack through the routing layer, netfilter (iptables/nftables), and finally to the user-space application.

Allocating millions of sk_buff structures per second requires immense CPU overhead. If you use iptables to block an attack of 10 million packets per second (Mpps), your CPU will bottleneck on memory allocation and context switching long before it even reads your firewall rule.

The Solution: eXpress Data Path (XDP) paired with eBPF. XDP allows you to attach custom code directly to the NIC driver. The code inspects the packet the millisecond it hits the hardware and can issue a XDP_DROP command before the kernel allocates an sk_buff. This allows a standard EPY Host dedicated server to drop millions of packets per second with virtually zero CPU load.

Prerequisites and Architecture Overview

To follow this tutorial, you will need:

  • A Dedicated Server: A high-bandwidth EPY Host bare metal server acting as the "Scrubber".

  • Operating System: Ubuntu 24.04 LTS or AlmaLinux 9 (this guide uses Ubuntu).

  • A Backend Server: The actual application server you are protecting.

  • Network Control: Your own ASN and IP prefix (e.g., a /24 block) to announce via BGP, or routing control from your upstream provider to steer targeted IPs to your scrubber.

The Architecture:

  1. Your Scrubber server announces the targeted IP space via BGP using FRR.

  2. Attack traffic flows to the Scrubber.

  3. The XDP/eBPF program instantly drops malicious packets (e.g., UDP port 53 amplification).

  4. The remaining "clean" traffic is forwarded to your Backend Server via a GRE (Generic Routing Encapsulation) tunnel.

Installing and Configuring FRRouting (BGP)

FRRouting (FRR) is a powerful, open-source routing suite. We will use it to establish a BGP session with your upstream provider so that traffic destined for your application flows through this scrubbing gateway first.

1. Install FRR:

bash

sudo apt update
sudo apt install frr frr-pythontools -y
                                    

2. Enable the BGP Daemon:

Edit the daemon configuration file to enable BGP:

bash

sudo nano /etc/frr/daemons
                                    

Find the line bgpd=no and change it to bgpd=yes. Save and restart the service:

bash

sudo systemctl restart frr
                                    

3. Configure BGP Peering:

Enter the FRR integrated shell (vtysh) to configure your routing:

bash

sudo vtysh
                                    

Run the following commands (replace the ASNs and IP addresses with your actual network data):

plaintext

conf t
router bgp 65000
 no bgp ebgp-requires-policy
 neighbor 192.168.1.1 remote-as 64512
 neighbor 192.168.1.1 description Upstream-Provider
 address-family ipv4 unicast
  network 203.0.113.0/24
 exit-address-family
exit
write memory
exit
                                    

Your scrubbing server is now announcing the 203.0.113.0/24 network to the world. All traffic for those IPs will hit this server.

Understanding eBPF and XDP Filtering

eBPF (Extended Berkeley Packet Filter) allows you to run sandboxed programs safely inside the Linux kernel without changing kernel source code or loading standard modules.

When you write an eBPF program for XDP, you are instructing the network card on how to handle raw network frames. The program parses the Ethernet header, then the IP header, and finally the TCP/UDP header. If the packet matches your attack signature (for instance, massive inbound UDP traffic on a specific port), the program returns XDP_DROP. If the packet is legitimate, it returns XDP_PASS, allowing it to proceed to the standard Linux networking stack.

To compile these programs, you need the appropriate development tools:

bash

sudo apt install clang llvm libbpf-dev linux-headers-$(uname -r) build-essential -y
                                    

Writing the eBPF C Program for Mitigation

Let us write a program to mitigate a UDP amplification attack. In this example, we will drop all incoming UDP packets originating from source port 53 (DNS) and port 123 (NTP).

Create a new file named xdp_ddos_drop.c:

bash

nano xdp_ddos_drop.c
                                    

Add the following C code. This code carefully parses the packet headers from the raw memory pointers provided by the XDP framework.

c

#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <arpa/inet.h>
#include <bpf/bpf_helpers.h>

SEC("xdp")
int xdp_drop_udp_amp(struct xdp_md *ctx) {
    // Define pointers to the start and end of the packet data
    void *data_end = (void *)(long)ctx->data_end;
    void *data = (void *)(long)ctx->data;

    // Parse the Ethernet header
    struct ethhdr *eth = data;
    if ((void *)(eth + 1) > data_end)
        return XDP_PASS;

    // Only inspect IPv4 packets
    if (eth->h_proto != __constant_htons(ETH_P_IP))
        return XDP_PASS;

    // Parse the IP header
    struct iphdr *iph = (void *)(eth + 1);
    if ((void *)(iph + 1) > data_end)
        return XDP_PASS;

    // Check if the protocol is UDP (Protocol 17)
    if (iph->protocol == IPPROTO_UDP) {
        // Parse the UDP header
        struct udphdr *udp = (void *)iph + (iph->ihl * 4);
        if ((void *)(udp + 1) > data_end)
            return XDP_PASS;

        // Drop DNS (53) and NTP (123) amplification traffic
        if (udp->source == __constant_htons(53) || udp->source == __constant_htons(123)) {
            return XDP_DROP; // Packet dropped at wire speed!
        }
    }

    // Allow all other traffic to pass to the OS
    return XDP_PASS;
}

char _license[] SEC("license") = "GPL";
                                    
    • Note: Security code running in the kernel must adhere strictly to boundaries; notice how we check if the headers exceed data_end before reading them. If you skip this, the eBPF verifier will reject the program to prevent kernel panics.

Compiling and Attaching the XDP Program

Now, compile the C code into an eBPF object file using the clang compiler:

bash

clang -O2 -g -Wall -target bpf -c xdp_ddos_drop.c -o xdp_ddos_drop.o
                                    

You should now have an xdp_ddos_drop.o file in your directory. To attach this to your network interface (e.g., eth0 or eno1), use the ip command.

First, identify your primary public network interface:

bash

ip link show
                                    

Assume your interface is eno1. Attach the compiled XDP program:

bash

sudo ip link set dev eno1 xdp obj xdp_ddos_drop.o sec xdp
                                    

If successful, running ip link show eno1 will now display xdp in the interface description. Your EPY Host bare metal server is now actively dropping malicious UDP amplification traffic before the Linux kernel even allocates memory for it.

To remove the filter later, you can run:

bash

sudo ip link set dev eno1 xdp off
                                    

Routing Clean Traffic via GRE Tunnels

Now that the attack traffic is being dropped at wire speed, the surviving legitimate traffic is passed up to the Linux networking stack of your scrubbing server. However, this server doesn't host your website or application—it just cleans the traffic.

We need to forward the clean traffic to your actual backend server without altering the original source IPs (so your application still logs the real visitors). We do this using a GRE tunnel.

On the Scrubber Server:

Run these commands to create a tunnel to the backend server (Assuming Scrubber IP is 10.0.0.1 and Backend IP is 10.0.0.2):

bash

# Enable IP forwarding
sudo sysctl -w net.ipv4.ip_forward=1

# Create the GRE tunnel
sudo ip tunnel add gre1 mode gre remote 10.0.0.2 local 10.0.0.1 ttl 255
sudo ip link set gre1 up
sudo ip addr add 192.168.100.1/30 dev gre1

# Route the protected prefix traffic through the tunnel
sudo ip route add 203.0.113.0/24 dev gre1
                                    

On the Backend Application Server:

Set up the other side of the tunnel to receive the traffic and route responses back through the scrubber.

bash

sudo ip tunnel add gre1 mode gre remote 10.0.0.1 local 10.0.0.2 ttl 255
sudo ip link set gre1 up
sudo ip addr add 192.168.100.2/30 dev gre1

# Ensure return traffic goes back out the tunnel
sudo ip route add default via 192.168.100.1 table 100
sudo ip rule add from 203.0.113.0/24 table 100
                                    

With this setup, the clean packet arrives at the scrubber, enters the GRE tunnel, and emerges at your application server with its original source IP intact.

Monitoring and Post-Mitigation Cleanup

Once your eBPF program is running, standard tools like tcpdump won't see the dropped traffic on the interface because XDP eliminates it before tcpdump (which relies on sk_buff structures) gets a chance to look.

To monitor how many packets your program is dropping, you can use the bpftool utility.

Install the tool:

bash

sudo apt install linux-tools-common linux-tools-generic -y
                                    

List the loaded BPF programs:

bash

sudo bpftool prog show
                                    

You will see your xdp_drop_udp_amp program listed with an ID. While building advanced statistics tracking into eBPF requires a concept called "BPF Maps," simply watching your system's overall bandwidth charts in your EPY Host control panel will reveal the massive ingress traffic, while your backend server will only show the clean, scrubbed traffic.

Final Thoughts

Building a custom scrubbing center with eBPF and FRRouting gives you unparalleled control over DDoS mitigation. Instead of relying on expensive third-party scrubbing subscriptions, you can leverage the raw hardware power of an EPY Host dedicated server to defeat high-volume attacks right at the network edge.