eBPF for Linux Admins: Part IX
Table of Contents
eBPF - This article is part of a series.
In this chapter, we will look at how to use eBPF maps to pass information to user space programs.
Also we will write an ebpf
loader program instead of using bpftool
this time 😃
BPF Loader#
The ebpf loader program will perform below with the help of libbpf
library.
- Loads the bpf object code
- Attaches the bpf program to an event
- Create maps
- Attach map to the ebpf program
- Poll information from map
eBPF maps#
Maps are a generic data structure for storage of different types of data. They allow sharing of data between eBPF kernel programs, and also between kernel and user-space applications.
Each map type has the following attributes: • type • maximum number of elements • key size in bytes • value size in bytes
You can read more about eBPF maps in the man pages. https://man7.org/linux/man-pages/man2/bpf.2.html
There are many types of maps supported by eBPF https://elixir.bootlin.com/linux/v6.7.4/source/include/uapi/linux/bpf.h#L906
But in this article we will be using a new type of map called, BPF_MAP_TYPE_RINGBUF
.
You can read more about this map in below link. https://www.kernel.org/doc/html/next/bpf/ringbuf.html
The loader code#
Please read the inline-comments in the code to understand what is happening in the loader.
vi kprobe_openat_user.c
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include <unistd.h>
#define TASK_COMM_LEN 16
#define FILE_NAME_LEN 256
// Placeholder for type casting ring-buffer data
struct event
{
__u32 e_pid;
char e_filename[FILE_NAME_LEN];
char e_comm[TASK_COMM_LEN];
};
// Function that translates the ring-buffer data and prints to console
static int
event_logger (void *ctx, void *data, size_t len)
{
struct event *evt = (struct event *) data;
printf ("do_sys_openat2 called by:%s file:%s pid:%d\n", evt->e_comm,
evt->e_filename, evt->e_pid);
return 0;
}
// Main
int
main ()
{
const char *file = "kprobe_openat.ebpf.o";
struct bpf_object *ebpf_object;
struct bpf_program *ebpf_prog;
struct bpf_link *ebpf_link = NULL;
int err, map_fd;
struct ring_buffer *rb;
// Open the ebpf object code
ebpf_object = bpf_object__open_file (file, NULL);
if (!ebpf_object)
{
printf ("Unable to open ebpf program");
return 1;
}
// Load the ebpf object code
err = bpf_object__load (ebpf_object);
if (err)
{
printf ("Failed to load ebpf program");
return 1;
}
// The find the relevant ebpf function from object code
ebpf_prog =
bpf_object__find_program_by_name (ebpf_object, "kprobe__do_sys_openat2");
if (!ebpf_prog)
{
printf ("Failed to find program name");
return 1;
}
// Auto-attach the ebpf program to the kprobe
ebpf_link = bpf_program__attach (ebpf_prog);
if (!ebpf_link)
{
printf ("Failed to attach ebpf");
return 1;
}
// Get the file descriptor to the map inside the ebpf object
map_fd = bpf_object__find_map_fd_by_name (ebpf_object, "rb");
// Create new ring-buffer and map the fd to the new rb and attach event_logger for polling
rb = ring_buffer__new (map_fd, event_logger, NULL, NULL);
if (!rb)
{
puts ("Failed to create rb");
return 1;
}
while (1)
{
// Read the data from ring-buffer (polling by event_logger)
ring_buffer__consume (rb);
sleep (1);
}
return 0;
}
eBPF program#
vi kprobe_openat.ebpf.c
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <string.h>
#define TARGET_NAME "sample_write"
#define MAX_ENTRIES 10
#define FILE_NAME_LEN 256
// Structure to store the data that we want to pass to user
struct event
{
u32 e_pid;
char e_filename[FILE_NAME_LEN];
char e_comm[TASK_COMM_LEN];
};
// eBPF map reference
struct
{
__uint (type, BPF_MAP_TYPE_RINGBUF);
__uint (max_entries, 256 * 1024);
} rb SEC (".maps");
// The ebpf auto-attach logic needs the SEC
SEC ("kprobe/do_sys_openat2")
int kprobe__do_sys_openat2 (struct pt_regs *ctx)
{
char filename[256];
char comm[TASK_COMM_LEN] = { };
struct event *evt;
const char fmt_str[] = "do_sys_openat2 called by:%s file:%s pid:%d";
// Reserve the ring-buffer
evt = bpf_ringbuf_reserve (&rb, sizeof (struct event), 0);
if (!evt)
{
return 0;
}
// Get the PID of the process.
evt->e_pid = bpf_get_current_pid_tgid (); // Get current process PID
// Read the filename from the second argument
// The x86 arch/ABI have first argument in di and second in si registers (man syscall)
bpf_probe_read (evt->e_filename, sizeof (filename), (char *) ctx->si);
// Read the current process name
bpf_get_current_comm (evt->e_comm, sizeof (comm));
// Compare process name with our "sample_write" name
if (memcmp (evt->e_comm, TARGET_NAME, 12) == 0)
{
// Print a message with filename, process name, and PID
bpf_trace_printk (fmt_str, sizeof (fmt_str), evt->e_comm,
evt->e_filename, evt->e_pid);
// Also send the same message to the ring-buffer
bpf_ringbuf_submit (evt, 0);
return 0;
}
// If the program name is not matching with TARGET_NAME, then discard the data
bpf_ringbuf_discard (evt, 0);
return 0;
}
char _license[] SEC ("license") = "GPL";
Compilation steps#
- Generate the types header from running kernel’s BTF.
bpftool btf dump file /sys/kernel/btf/vmlinux format c >vmlinux.h
- Compile the user-space program
clang -O2 -g -Wall kprobe_openat_user.c -o kprobe_openat_user -lbpf
- Compile the kernel-space eBPF re-locatable object code
clang -v -O2 -g -Wall -target bpf -c kprobe_openat.ebpf.c -o kprobe_openat.ebpf.o
Execution and monitoring#
- Open another terminal to watch the trace pipe so that we can compare that with the ring-buffer data.
sudo cat /sys/kernel/debug/tracing/trace_pipe
- Execute the user-space program and the program will not show any output this time.
sudo ./kprobe_openat_user
- Now, execute the
sample_write
that we created in Chapter 6.
./sample_write
- Output of the user-space program.
do_sys_openat2 called by:sample_write file:/etc/ld.so.cache pid:2361
do_sys_openat2 called by:sample_write file:/lib/x86_64-linux-gnu/libc.so.6 pid:2361
do_sys_openat2 called by:sample_write file:./sample.txt pid:2361
- Output of trace pipe
<...>-2361 [002] ...21 96607.710040: bpf_trace_printk: do_sys_openat2 called by:sample_write file:/etc/ld.so.cache pid:2361
<...>-2361 [002] ...21 96607.710082: bpf_trace_printk: do_sys_openat2 called by:sample_write file:/lib/x86_64-linux-gnu/libc.so.6 pid:2361
sample_write-2361 [002] ...21 96607.710316: bpf_trace_printk: do_sys_openat2 called by:sample_write file:./sample.txt pid:2361
This conclude the primer to ebpf for linux Admin series.
Foot Note:-
I’m neither an expert in eBPF nor a Kernel developer. I’m an ops guy with an enthusiasm to learn new technologies. My intention was to give an intro for eBPF which can easily connect with Linux admins. The programs given in this series are used to showcase some of the capabilities of eBPF and should not be used for production use. There are a tone of information/details to be discussed to uncover eBPF Please refer the eBPF/Linux Kernel docs for further reading
Some parts of the codes in this article were inspired by the Falco Blog