eBPF (Extended Berkeley Packet Filter)
eBPF makes the kernel programmable, so you don’t need to change the official linux kernel (not that easy 😄)
Table of Contents
Open Table of Contents
Hello World Programm:
#!/usr/bin/python3
from bcc import BPF
program =r"""
int hello(void *ctx) {
bpf_trace_printk("Hello World!");
return 0;
}
"""
b =BPF(text=program)
syscall =b.get_syscall_fnname("execve")
b.attach_kprobe(event=syscall,fn_name="hello")
b.trace_print()
The Programm here gets compiled in Python, load into the kernel, and attached to a kprobe that will be hit whenever the execve
system call runs. (execve
system call is used to execute a program) This is the actual cBPF code:
int hello(void *ctx) {
bpf_trace_printk("Hello World!");
return 0;
}
This looks like a C function called hello()
. Execute the programm like this:
./hello.py
Now if you go into another terminal and run a command you can see this output:
eBPF Maps
eBPF Maps are data structures that can be accessed from within eBPF programs in the kernel, and from user space applications. They can be used to share information between eBPF programs and with user space code - for example, to pass configuration into an eBPF program, or to send observability data collected in the kernel to user space.
Maps attributes:
- type
- maximum number of elements
- key size in bytes
- value size in bytes
Create a map:
Program creates a hash table, which stores key-value pairs:
BPF_HASH(counter_table);
In the user space code we can access it like this:
while True:
sleep(2)
s = ""
for k,v in b["counter_table"].items():
s += f"ID {k.value}: {v.value}\t"
print(s)
Maps usecase:
#!/usr/bin/python3
from bcc import BPF
from time import sleep
program = r"""
BPF_HASH(counter_table);
int hello(void *ctx) {
u64 uid;
u64 counter = 0;
u64 *p;
uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
p = counter_table.lookup(&uid);
if (p != 0) {
counter = *p;
}
counter++;
counter_table.update(&uid, &counter);
return 0;
}
"""
b = BPF(text=program)
syscall = b.get_syscall_fnname("execve")
b.attach_kprobe(event=syscall, fn_name="hello")
# Attach to a tracepoint that gets hit for all syscalls
# b.attach_raw_tracepoint(tp="sys_enter", fn_name="hello")
while True:
sleep(2)
s = ""
for k,v in b["counter_table"].items():
s += f"ID {k.value}: {v.value}\t"
print(s)
Get user ID and group ID with helper function:
uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
Lookup in the counter_table
map to see if there is already an entry with the key that matches this user ID:
p = counter_table.lookup(&uid);
Counter gets incremented and the key-value pair (uid-counter) gets written to the map.
counter++;
counter_table.update(&uid, &counter);
eBPF in k8s
- all containers share one kernel