Creating a basic eBPF app in C

This is a basic eBPF program binding xdp on your preferred interface (default is lo), and will print some trace in /sys/kernel/debug/tracing/trace_pipe for each packet received.

Kernel Link to heading

The eBPF program (ie: kernel part) is looking as the following:

#include <stddef.h>

#include <linux/bpf.h>
#include <linux/in.h>
#include <linux/if_ether.h>
#include <linux/ip.h>

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

SEC("xdp")
int basic(struct xdp_md *ctx)
{
    bpf_printk("hello xdp");

    return XDP_PASS;
}

char _license[] SEC("license") = "GPL";

Userland Link to heading

#include <stdio.h>
#include <unistd.h>
#include <net/if.h>

#include "basic_kern.skel.h"

int main(int argc, char **argv)
{
    char *interface_name = "lo";
    struct basic_kern *skel;
    int ret = 0;

    if (argc > 1) {
        interface_name = argv[argc - 1];
    }

    unsigned int ifindex = if_nametoindex(interface_name);
    
    if (ifindex == 0) {
        fprintf(stderr, "ERROR: failed to retrieve interface index of %s", interface_name);
        return 1;
    }

    skel = basic_kern__open();
    if (!skel) {
        fprintf(stderr, "ERROR: failed to open BPF skeleton\n");
        return 1;
    }

    ret = basic_kern__load(skel);
    if (ret) {
        fprintf(stderr, "Failed to load BPF skeleton\n");
        goto cleanup;
    }

    bpf_program__attach_xdp(skel->progs.basic, ifindex);

    // ... read the trace output, sleep, do whatever is needed to avoid program to quit

cleanup:
    basic_kern__destroy(skel);
    return ret;
}

Note: the basic_kern.skel.h file is generated with bpftool:

$ bpftool gen skeleton basic_kern.o > basic_kern.skel.

Makefile Link to heading

TARGET=basic

USER_TARGET = ${TARGET:=_user}
BPF_TARGET = ${TARGET:=_kern}
BPF_C = ${BPF_TARGET:=.c}
BPF_OBJ = ${BPF_C:.c=.o}

all: clean kern $(USER_TARGET)

kern: $(BPF_OBJ)
	llvm-strip -g $(BPF_OBJ)
	bpftool gen skeleton $(BPF_OBJ) > $(BPF_TARGET).skel.h

$(USER_TARGET): %: %.c  
	gcc -Wall $(CFLAGS) -Ilibbpf/src -Ilibbpf/src/include/uapi -Llibbpf/src -o $@ $< -lbpf -lelf -lz

$(BPF_OBJ): %.o: %.c
	clang -g -O2 -target bpf -c $< -o $@

clean:
	rm -f $(BPF_OBJ) $(USER_TARGET) $(BPF_TARGET).skel.h

run: $(USER_TARGET) kern
	sudo ./$(USER_TARGET)

.PHONY: all kern clean run

Building and running: Link to heading

$ ls
basic_kern.c  basic_user.c  Makefile

$ make
rm -f basic_kern.o basic_user basic_kern.skel.h
clang -g -O2 -target bpf -c basic_kern.c -o basic_kern.o
llvm-strip -g basic_kern.o
bpftool gen skeleton basic_kern.o > basic_kern.skel.h
gcc -Wall  -Ilibbpf/src -Ilibbpf/src/include/uapi -Llibbpf/src -o basic_user basic_user.c -lbpf -lelf -lz

$ sudo ./basic_user lo
...

In another terminal:

$ ping -c 1 localhost
PING localhost (::1) 56 data bytes
64 bytes from localhost (::1): icmp_seq=1 ttl=64 time=0.178 ms

--- localhost ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.178/0.178/0.178/0.000 ms

… this will print in a console tailing the tracing_pipe file:

           <...>-22854   [020] ..s21  4323.273485: bpf_trace_printk: hello xdp
           <...>-22854   [020] ..s21  4323.273547: bpf_trace_printk: hello xdp
 DNS Res~ver #37-16290   [004] ..s21  4327.952397: bpf_trace_printk: hello xdp
           <...>-1679    [003] ..s21  4327.984450: bpf_trace_printk: hello xdp
 DNS Res~ver #30-8354    [014] ..s21  4331.159694: bpf_trace_printk: hello xdp
 systemd-resolve-1679    [003] ..s21  4331.160336: bpf_trace_printk: hello xdp
 DNS Res~ver #36-16289   [013] ..s21  4348.184675: bpf_trace_printk: hello xdp
 systemd-resolve-1679    [003] ..s21  4348.194696: bpf_trace_printk: hello xdp
           <...>-7522    [019] ..s21  4349.562855: bpf_trace_printk: hello xdp
 systemd-resolve-1679    [003] ..s21  4349.563329: bpf_trace_printk: hello xdp

Misc Link to heading

$ sudo bpftool prog
107: xdp  name basic  tag 0382e177d377c3ff  gpl
	loaded_at 2024-09-23T17:06:59+0200  uid 0
	xlated 48B  jited 47B  memlock 4096B  map_ids 25
	btf_id 242
	pids basic_user(23779)
...