Overview
This is a quick guide to get started with XDP.
Detailed explanations of eBPF and other concepts are omitted.
XDP is a framework for processing packets at the earliest stage of the Linux kernel’s networking stack, allowing programs to be directly inserted into the NIC (Network Interface Card) using eBPF.
Since XDP operates on packets at the earliest stage of the Linux kernel’s networking stack, it can process them faster than filters like iptables.
In short, think of XDP as a way to attach programs to the NIC interface using eBPF.
Setting Up the Environment
This guide is based on Ubuntu 22.04.
Since XDP controls NIC communication, it’s recommended to try this on a virtual machine or similar setup.
Install necessary packages
$ sudo apt install build-essential clang llvm gcc-multilib libbpf-dev
Building eBPF (XDP) requires clang instead of gcc, and the libbpf-dev package for eBPF header files.
gcc-multilib is needed later for using asm/types.h
.
Let’s Try Dropping All Packets
Checking the Interface
First, let’s see if there is any communication on the interface
$ sudo tcpdump -i eth0 -n
Typically, if connected to a switch, you should see ARP traffic, etc.
If no traffic is observed, try sending a ping from an external source to check for packets.
Preparing the XDP Program
Drop all traffic using XDP
- xdp.c
#include <linux/bpf.h> #include <bpf/bpf_helpers.h> SEC("xdp") int xdp_drop(struct xdp_md *ctx) { return XDP_DROP; } char _license[] SEC("license") = "MIT";
#include <linux/bpf.h>
- Provides Linux BPF (Berkeley Packet Filter) related functionality
#include <bpf/bpf_helpers.h>
- Provides helper functions needed for writing eBPF programs
SEC("xdp")
- A macro specifying the type of eBPF program for the following function
- Specifying “xdp” indicates that the following function is an XDP program
int xdp_drop(struct xdp_md *ctx)
- The xdp_drop function is called every time a packet is received.
In this case, it returnsXDP_DROP;
, dropping all packets
- The xdp_drop function is called every time a packet is received.
char _license[] SEC("license") = "MIT";
- An eBPF program needs a license to be executed
- If using functions derived from GPL, the license must be GPL or an error occurs
Building
Build the program
$ clang -O2 -target bpf -c xdp.c -o xdp.o
By specifying bpf as the target, the program is built as an eBPF object.
Attaching to the Interface
Attach the program to the interface
$ sudo ip link set dev eth0 xdp obj xdp.o sec xdp
Checking the Packets
$ sudo tcpdump -i eth0 -n
You should no longer see ARP, ICMP, or other packets.
Since XDP processes packets before tcpdump, you won’t be able to observe packets with tcpdump.
Detaching XDP
$ sudo ip link set dev eth0 xdp off
This detaches the XDP program that drops all packets,
allowing communication to resume.
A Deeper Look (Dropping ICMP)
Dropping all packets is almost the same as ip link set down dev eth0
and isn’t very practical.
Let’s use XDP to drop only ICMP packets.
- xdp.c
#include <linux/bpf.h> #include <bpf/bpf_helpers.h> #include <linux/if_ether.h> #include <linux/ip.h> #include <linux/icmp.h> #include <arpa/inet.h> SEC("xdp") int xdp_drop_icmp(struct xdp_md *ctx) { void *data_end = (void *)(unsigned long)ctx->data_end; void *data = (void *)(unsigned long)ctx->data; // Get a pointer to the Ethernet header struct ethhdr *eth = data; if ((void *)eth + sizeof(*eth) > data_end) return XDP_PASS; // Check if the Ethernet frame contains an IP packet if (eth->h_proto != htons(ETH_P_IP)) return XDP_PASS; // Get a pointer to the IP header struct iphdr *ip = data + sizeof(*eth); if ((void *)ip + sizeof(*ip) > data_end) return XDP_PASS; // Check if the protocol is ICMP if (ip->protocol != IPPROTO_ICMP) return XDP_PASS; // Get a pointer to the ICMP header struct icmphdr *icmp = (void *)ip + sizeof(*ip); if ((void *)icmp + sizeof(*icmp) > data_end) return XDP_PASS; // Drop the ICMP packet return XDP_DROP; } char _license[] SEC("license") = "MIT";
The context (ctx) allows access to packet pointers for processing.
- data
- Pointer to the packet data
- data_end
- Pointer to the end of the packet data
struct ethhdr *eth = data;
if ((void *)eth + sizeof(*eth) > data_end)
return XDP_PASS;
The data pointer is assigned to the Ethernet header.
If the pointer plus the Ethernet header size exceeds data_end,
the packet size is smaller than the Ethernet header, so it is passed to the Kernel without processing.
if (eth->h_proto != htons(ETH_P_IP))
return XDP_PASS;
Check if the Ethernet header’s h_proto field is ETH_P_IP (IPv4 protocol 0x0800). The htons macro converts the host’s endianness (host byte order) to the network packet’s endianness (network byte order). htons stands for “host to network short”.
If the protocol contained in the Ethernet packet is not IPv4, the packet is passed to the Kernel with XDP_PASS.
struct iphdr *ip = data + sizeof(*eth);
if ((void *)ip + sizeof(*ip) > data_end)
return XDP_PASS;
Get a pointer to the IP header. Adding the Ethernet header size to the initial data pointer gives the pointer to the start of the IP header.
If the IP header pointer plus the IP header size exceeds data_end,
it is not an IP header, so the packet is passed to the Kernel with XDP_PASS.
if (ip->protocol != IPPROTO_ICMP)
return XDP_PASS;
Check if the protocol in the IP packet is IPPROTO_ICMP (1).
Protocols other than ICMP are passed to the Kernel with XDP_PASS.
struct icmphdr *icmp = (void *)ip + sizeof(*ip);
if ((void *)icmp + sizeof(*icmp) > data_end)
return XDP_PASS;
Get a pointer to the ICMP header.
Advance the pointer from the start of the IP header by the size of the IP header.
Also check if it exceeds data_end.
Packets that haven’t been passed with XDP_PASS by this point are ICMP packets,
so they are dropped with XDP_DROP to achieve the desired outcome.
Note: You might drop the packet already at the IPPROTO_ICMP check.
Conclusion
Although writing in C might feel a bit challenging,
you will find that it can be accomplished with surprisingly simple code once you try it.