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 を作成するときのバリデーションで引っかかってそうなことが分かった。
ここでは map を作る際に attr をバリデーションしている。この関数の内容を見ると btf_key_type_id
と btf_value_type_id
が 0 だと EINVAL になるようになっている。
strace の結果を見るとたしかに btf_{key,value}_type_id
がたしかに 0 になっており、落ちそう。
btf_{key,value}_type_id
がないのはなぜか?
btf_{key,value}_type_id
は BTF 関係のパラメータで、要するに 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ことに注意。