Unyablog.

のにれんのブログ

eBPF: BPF_MAP_TYPE_SK_STORAGE が Invalid argument

Linux の eBPF で BPF_MAP_TYPE_SK_STORAGE を使おうとして、map を定義して bpftool で流し込んだけど Invalid argument でうまく動かなかった。調べてもヒットしなくて長時間費やしたのでメモ。

環境は ArchLinux で Kernel release は 5.7.10-arch1-1

TL;DR

BTF に対応する必要がある。

詳細

struct bpf_map_def SEC("maps") sk_storage_map = {
  .type = BPF_MAP_TYPE_SK_STORAGE,
  .map_flags = BPF_F_NO_PREALLOC,
  .key_size = sizeof(int),
  .value_size = sizeof(int),
}; 

このような map を作って、それを使ったプログラムを load しようとしたところ下のエラーが。

$ sudo bpftool prog load sk_storage.o /sys/fs/bpf/sk_storage
...
libbpf: failed to create map (name: 'sk_storage_map'): Invalid argument(-22)
...

コンパイルできるのに動かないので、自分の環境がおかしいのかと思って テスト を走らせてみたけど、それはちゃんと PASS した。 BPF_TYPE_MAP_SK_STORAGE 自体は使えるらしい。

ただ、そのテストでは bpf_create_map_xattr を使って C 言語から直接 map を作成していた。自分は bpftool を使いたいので単純に真似することはできない。

まず、エラーになってる原因を探るために strace する。

$ sudo strace bpftool prog load sk_storage.o /sys/fs/bpf/sk_storage
...
bpf(BPF_MAP_CREATE, {map_type=BPF_MAP_TYPE_UNSPEC, key_size=0, value_size=0, max_entries=0, map_flags=0, inner_map_fd=0, map_name="sk_storage_map", map_ifindex=0, btf_fd=0, btf_key_type_id=0, btf_value_type_id=0, btf_vmlinux_value_type_id=0}, 120) = -1 EINVAL (Invalid argument)
write(2, "libbpf: failed to create map (na"..., 73libbpf: failed to create map (name: 'sk_storage_map'): Invalid argument(-22)
) = 73
...

strace すると BPF の argument も見れるんですね、便利。どうやら bpftool は prog を load しているだけで、実際に map を作っているのは libbpf らしい。

ただ、これだけじゃなぜ EINVAL が出ているのか分からない。その後カーネルのコードを探っていくと BPF_MAP_TYPE_SK_STORAGE の map を作成するときのバリデーションで引っかかってそうなことが分かった。

github.com

ここでは map を作る際に attr をバリデーションしている。この関数の内容を見ると btf_key_type_idbtf_value_type_id が 0 だと EINVAL になるようになっている。

strace の結果を見るとたしかに btf_{key,value}_type_id がたしかに 0 になっており、落ちそう。

btf_{key,value}_type_id がないのはなぜか?

btf_{key,value}_type_idBTF 関係のパラメータで、要するに key と value の型情報を残す必要があるらしい。

そうするには、 https://www.kernel.org/doc/html/latest/bpf/btf.html#bpf-map-create にあるように bpf_map_def に対して BPF_ANNOTATE_KV_PAIR を使ってもいいし、bpf_helpers にある __type を使ったやり方でもいい。

bpf_helpers にある __type を使ったやり方だとこうなる。

struct {
  __uint(type, BPF_MAP_TYPE_SK_STORAGE);                                                                                                                                                         
  __uint(map_flags, BPF_F_NO_PREALLOC);
  __type(key, int);                                                                                                                                                                              
  __type(value, int);
} sk_storage_map SEC(".maps");

SEC 名を "maps" じゃなくて ".maps" (MAPS_ELF_SEC) にする*1ことに注意。