Monthly Archives: February 2025

eBPF (Extended Berkeley Packet Filter) for dummies

This is a eBPF simple primer post written with generous help from Claude.

ELI5 version

eBPF is like having magic glasses for your computer. These glasses let you see what’s happening inside your computer without stopping it or slowing it down. You can watch programs talk to each other, see how fast things are moving, and even catch bad behaviors. The best part is you can program these glasses to look for specific things and take action when they happen.

What is eBPF?

eBPF is a technology in the Linux kernel that allows you to run small programs in a safe, sandboxed environment directly in the kernel. It was originally designed for network packet filtering but has evolved into a powerful, general-purpose monitoring and tracing framework.

Key features:

  • Runs safely inside the kernel without modifying kernel code
  • High performance with minimal overhead
  • Versatile application across networking, security, and observability
  • JIT (Just-In-Time) compilation for near-native performance

eBPF Tools Ecosystem

  1. BCC (BPF Compiler Collection): A toolkit for creating eBPF programs using Python and Lua frontends.
  2. bpftrace: A high-level tracing language for eBPF, similar to awk or DTrace. It provides a simple, powerful scripting interface for writing eBPF programs.
  3. Cilium: Uses eBPF for container networking, observability, and security.
  4. Falco: Security monitoring tool that uses eBPF to detect anomalous behavior.
  5. Hubble: Network and security observability platform built on eBPF.
  6. Pixie: Observability platform for Kubernetes applications using eBPF.

What is bpftrace?

bpftrace is a high-level tracing language for eBPF that makes it easy to write small programs to trace and analyze system behavior. Think of bpftrace as the friendly interface to eBPF’s power.

Relationship to eBPF:

  • bpftrace is to eBPF what SQL is to a database engine
  • It compiles your human-readable scripts into eBPF bytecode
  • Handles the complexity of loading and running your eBPF programs
  • Provides built-in functions and easy syntax for common tracing needs

Simple bpftrace example:

# Count system calls by process name
bpftrace -e 'tracepoint:syscalls:sys_enter_* { @[comm] = count(); }'

This one-liner counts all system calls grouped by process name, demonstrating bpftrace’s concise yet powerful syntax.

Kprobes and Uprobes

Kprobes

Kprobes (Kernel Probes) are debugging mechanisms in the Linux kernel that allow you to dynamically break into any kernel routine and collect debugging and performance information non-disruptively. They’re essentially dynamic breakpoints you can insert anywhere in the kernel code.

Key features:

  • Can be attached to virtually any instruction in the kernel
  • Minimal performance impact when not triggered
  • Collect register and memory state at the probe point
  • Available in two flavors: kprobes (at function entry) and kretprobes (at function return)

Uprobes

Uprobes (User Probes) are similar to kprobes but work in userspace. They allow you to trace and instrument user applications by inserting breakpoints at specific functions or instructions.

Key features:

  • Trace applications without modifying their source code
  • Attach to specific functions in userspace programs
  • Monitor application behavior in production
  • Available as both uprobes (function entry) and uretprobes (function return)

Relationship to eBPF

Kprobes and uprobes provide the attachment points for eBPF programs to hook into kernel and user application code. The relationship works like this:

  1. Attachment mechanism: eBPF programs use kprobes/uprobes as the “hooks” to insert themselves into kernel or application execution paths
  2. Data collection: When a probe is triggered, the associated eBPF program executes, collecting data and potentially making decisions
  3. Performance: eBPF added JIT compilation to make probe handlers extremely efficient
  4. Programmability: Before eBPF, probes were limited in functionality; eBPF adds a programmable layer to determine what happens when a probe triggers

An example in bpftrace showing both kprobe and uprobe:

# Trace kernel function
bpftrace -e 'kprobe:do_sys_open { printf("Opening file: %s\n", str(arg1)); }'

# Trace user function in libc
bpftrace -e 'uprobe:/lib/x86_64-linux-gnu/libc.so.6:malloc { printf("malloc called, size: %d\n", arg0); }'

eBPF transformed kprobes and uprobes from simple debugging tools into a powerful, programmable observability framework, turning them from basic breakpoints into sophisticated monitoring tools with minimal performance impact.

How to pin a specific apt package version

I’d like to pin a specific package, say redis-server, to a specific version, in my case 7.0.*, and that seems straight-forward to do with:

Package: redis-server
Pin: version 7.0.*
Pin-Priority: 1001

Now, I would also like to have apt fail when 7.0.* is not available, either because there are only newer versions available, f.ex. 7.2.* or 7.4.* or perhaps because only older versions like 6.* are available.

I can’t seem to find a way to achieve that. I’ve read various resources only, consulted man 5 apt_preferences, but I’m still not sure how to.

I tried combining the previous pinning rule to another one with priority -1, as in the following:

Package: redis-server
Pin: release *
Pin-Priority: -1

But that seems to make all versions unavailable unfortunately. Here’s what I’m seeing:

$ apt-cache policy redis-server
redis-server:
  Installed: (none)
  Candidate: 5:7.0.15-1build2
  Version table:
     5:7.0.15-1build2 500
        500 [http://no.archive.ubuntu.com/ubuntu](http://no.archive.ubuntu.com/ubuntu) noble/universe amd64 Packages

$ cat > /etc/apt/preferences.d/redis-server
Package: redis-server
Pin: version 7.0.15*
Pin-Priority: 1001

Package: redis-server
Pin: release *
Pin-Priority: -1

$ apt-cache policy redis-server
redis-server:
  Installed: (none)
  Candidate: (none)
  Version table:
     5:7.0.15-1build2 -1
        500 [http://no.archive.ubuntu.com/ubuntu](http://no.archive.ubuntu.com/ubuntu) noble/universe amd64 Packages

I expected this configuration to provide an available candidate, since one exists (7.0.15), but that doesn’t work.

Note that a successful outcome for me is:

  • define a target wanted version, f.ex. redis-server=7.0.*
  • provide an apt preferences.d file such that:
    • when any 7.0.* versions are available, apt will install that version
    • when no 7.0.* versions are available, apt will fail installing nothing

A bad outcome is when redis-server is installed, but with a package version that does not match what I had specified as requirement (hence, different from 7.0.*).

This is on Ubuntu 24.04, although there is nothing specific to 24.04 or Ubuntu here I would think.

Any ideas?

Posted on stackoverflow, let’s see! https://unix.stackexchange.com/questions/790837/how-to-pin-an-apt-package-to-a-version-and-fail-if-its-not-available

UPDATE: based on the stackoverflow feedback, it seems that the solution wasn’t far off.

Package: redis-server
Pin: version 5:7.0.15*
Pin-Priority: 1001

Package: redis-server
Pin: release *
Pin-Priority: -1

I needed to prepend the version with the "5:“.