Quick and dirty BPF map updates with bpftool

Thu Jan 23 '20

The bpftool utility makes easy work of reading and updating BPF maps. This post will start with a really simple BPF program that uses a map to determine whether a message should be printed on each packet that goes through a BPF program running on a route.

Dependencies

You’ll need to start by installing dependencies. Assuming you’re already set up for bpf development[1], you may just need the bpftool package.

zypper in bpftool

The BPF program

Here is a minimal[2] BPF program that has some maps we can use for demonstration. This example is meant to attach to a route using BPF_PROG_TYPE_LWT_OUT. But really, it’s not picky. It will attach to anything that is foolish enough to give it a skb and accept payment in the form of BPF_OK in return.

// lwt_out.c
#include <linux/bpf.h>
#include "include/bpf_helpers.h"

struct bpf_map_def SEC("maps") printk_map = {
    .type = BPF_MAP_TYPE_ARRAY,
    .key_size = sizeof(int),
    .value_size = sizeof(int),
    .max_entries = 1
};

SEC("lwt_out")
int prog_lwt_out(struct __sk_buff *skb)
{
    const char fmt[] = "Print about this skb boi\n";
    int *value, key = 0;

    value = bpf_map_lookup_elem(&printk_map, &key);
    if (value && !*value)
        bpf_trace_printk(fmt, sizeof(fmt));

    return BPF_OK;
}

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

In a nutshell[3], unless otherwise told by the value in the BPF_MAP_TYPE_ARRAY, this program will print on every single packet that leaves via the route it is attached to. Compile the program and install it to a route:

clang -O2 -g -Wall -Werror -target bpf -emit-llvm -c lwt_out.c -o - | \
    llc -march=bpf -filetype=obj -o lwt_out.o

pahole -J lwt_out.o

ip link add dum-dum type dummy

ip link set dum-dum up

ip route add 192.0.2.0/32 dev dum-dum encap bpf out obj lwt_out.o section lwt_out

Note pahole is optional but converts the DWARF format into BTF format for better debugging.

At this point, you should be able to ping 192.0.2.0 and see the printk output:

bpftool prog tracelog
ping-24366 [007] .... 18601.103214: 0: Print about this skb boi
ping-24366 [007] .... 18602.125616: 0: Print about this skb boi

Enter the maps

Okay. Obviously this blue part here is the land…

—Buster Bluth

Now that we have our super useful and not-at-all demonstrative program running, we need a way to turn logging on and off using the map we created in lwt_out.c above.

Lets first get an idea of what our map looks like. The map_ids field shows a list of maps attached to our program.

bpftool prog show id 85 | grep lwt_out -A2
85: lwt_out  tag 671a86d804002c58  gpl
    loaded_at 2020-01-21T21:56:21-0800  uid 0
    xlated 264B  jited 164B  memlock 4096B  map_ids 25

Knowing the map ID, we can show it.

bpftool map show id 25
25: array  flags 0x0
    key 4B  value 4B  max_entries 1  memlock 4096B

There’s a --json flag to output as JSON. There’s a --pretty flag you can use instead of piping to jq, but I find the latter prettier.

bpftool map dump id 25 -j | jq
[
  {
    "key": [
      "0x00",
      "0x00",
      "0x00",
      "0x00"
    ],
    "value": [
      "0x00",
      "0x00",
      "0x00",
      "0x00"
    ]
  }
]

Finally, updating the map is trivial with bpftool. The printk output should stop immediately upon entering this command.

bpftool map update id 25 key 0 0 0 0 value 1 0 0 0

Easy! Note that this will parse the byte sequence as decimal, but you can prefix it with 0x for hexadecimal, or 0 for octal. Alternatively, if you get sick of typing 0x in front of everything, just use the hex keyword in front of the byte sequence.

For technically-accurate and better-wrtten content on bpftool’s abilities around maps, check out man bpftool-map.

Now that you know how easy it is, get out there, and be creative!∎