XDP - A Simple Overview

Before we can understand XDP we first need to understand eBPF. In 2014, eBPF (extended Berkeley Packet Filter) was released into the Linux kernel, it allows developers to extend the kernel capabilities at runtime without compromising on safety or speed. It’s a common misconception that eBPF is only for networking, this is due to eBPF first being developed for packet filtering (as the name implies) and then growing to be a universal system for hooking onto events within the kernel.

To prevent eBPF programs from compromising the security of the kernel it is first checked by the eBPF verifier. This checks the programs for anything that could crash by interpreting the source code and rejecting the program if there are any signs of unwanted behaviour.

Once the verifier has given the thumbs up for the code to run it will then be JIT (Just-In-Time) compiled for execution. 

This execution is done in an event-driven architecture, meaning programs are written such that eBPF events are subscribed to and your program's code is executed when such events are produced. The most common use for eBPF is in networking and hooking into network events as soon as possible when processing the packets.

XDP (eXpress Data Path) is a highly used part of eBPF. It is the part of eBPF responsible for allowing the user to hook onto events that are dedicated to sending and receiving packets. The high use of XDP is due to the fact that the event occurs before the packet reaches most of the operating system networking stack. This allows for highly efficient packet processing and filtering. The event producer is built into the NIC (network interface controller) driver in-between the interrupt for receiving the packet and just before memory allocation in the networking stack.

Not all NICs support XDP! If you develop eBPF programs that use XDP and your NIC does not support the hook then the Linux kernel will fire the event as early as possible after memory allocation in the network stack. This has slower performance but is there for compatibility reasons. You may not even notice this has happened so it's always good to double-check to see if your NIC supports XDP.

There are 3 ways of connecting your XDP program each intersecting the packet at different layers. 

  • The best way is through Offloaded XDP which requires that your NIC supports XDP. This allows your NIC to execute the program without even using the CPU. This method typically requires specialised NICs.
  • The next best way if your NIC does not support XDP Offloading is via Native XDP. This allows your XDP program to be loaded by your network card driver. Once loaded events will be fired as soon as the packet reaches your network driver.
  • The fallback method if both your NIC and driver does not support XDP is the Generic XDP method. This is where the events are fired as part of the ordinary network stack and does not provide the full performance benefits as the other methods.

Going into detail

Now we know what eBPF and XDP are and the concept behind their implementation we can now discuss implementation specifications. Once the eBPF program is hooked onto the XDP hook, your code can edit and control what happens to the packet. The following list are the action codes you can return out of the program to choose what to do with the packet:

  • XDP_PASS: Packet continues to the network stack
  • XDP_DROP: Drop packet silently
  • XDP_ABORTED: Drop packet with tracepoint exception
  • XDP_TX: bounce back the packet
  • XDP_REDIRECT: redirect the packet to a different NIC

Some common uses for XDP eBPF programs are for DDoS protection, as other than dedicated hardware, XDP has been shown to be one of the most performant methods in dealing with packet filtering by applying the XDP_DROP action code. Other uses for XDP are for packet forwarding, load balancing and packet monitoring.


Setting up your first XDP program

To write your first program using XDP. First you will need to install a whole bunch of packages as the following:

apt install clang llvm libelf-dev libpcap-dev build-essentials linux-tools-$(uname -r) linux-headers-$(uname -r) linux-tools-common linux-tools-generic tcpdump 

Then you will need to clone and add xdp-tools and libbpf to your project from the following repos:

  • https://github.com/xdp-project/xdp-tools/
  • https://github.com/libbpf/libbpf/

Now you will need to create you first XDP program, the following code will drop all TCP packets except for the packets going to port 22... we don't want to kill your SSH session now do we?:

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

SEC("xdp")
int  xdp_prog_simple(struct xdp_md *ctx)
{
	void *data = (void *) (long) ctx->data;
    void *data_end = (void *) (long) ctx->data_end;
    
    struct ethhdr *eth = data;
    struct iphdr *ip = data + sizeof(*eth);
    
    if ((void *) ip + sizeof(*ip) <= data_end) {
        if (ip->protocol == IPPROTO_TCP) {
            struct tcphdr *tcp = (void *) ip + sizeof(*ip);
            if ((void *) tcp + sizeof(*tcp) <= data_end) {
                if (htons(tcp->dest) != 22) {
                    return XDP_DROP;
                }
            }
        }
    }
    return XDP_PASS;
}

Once you have created the program file called `xdp_first.c` with the above code we need to compile it targeting bpf with the following command:

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

Once compiled we can attach the program to a network interface of your choosing. Now be careful as running this will drop all packets meaning you will have to reboot the machine or detach the program. To load the program onto your interface use the following command:

ip link set <interface> <method> obj xdp_first.o sec xdp_first

Where <method> is either:

  • 'xdp': native XDP
  • 'xdpgeneric': generic XDP
  • 'xdpoffload': offloaded XDP

And just like that, you have created your first XDP program!! Congratulations

… did you think I would leave you hanging with a packet drop program?

ip link set <interface> <method> off