Open6

Traffic Controller (tc) に BPF をアタッチする時に知っておくべきこと

OpenJNYOpenJNY

OpenJNYOpenJNY

XDP との関係

XDP の eBPF アタッチポイントは以下の 3 種類存在する。

  1. H/W Offloading され、NIC で実行する eBPF (NIC のサポートが必要)
  2. Driver 内部で実行される eBPF (Driver のサポートが必要)
  3. Driver で処理された直後に実行される eBPF (Generic XDP と呼ばれ、任意の NIC/ドライバで実行可能)

ポイント:

  • XDP は ingress のみ対応
  • ingress に関しては、TC filter よりも XDP の方が先に実行される
  • XDP の eBPF プログラムは skb (sk_buff) になっていない生のパケットを処理するが、TC ではすでに skb のデータ構造で表現されたパケットを処理する

https://www.netronome.com/blog/open-source-packet-filtering-bpf-fosdem19/

OpenJNYOpenJNY

libbpf 対応の話

https://lwn.net/Articles/836911/

iproute2 での BPF 処理方法は古く、いくつかの最新機能に追従できないでいた。さらに公式のソースツリーで開発されている libbpf の登場で、libpbf への移行の機運がたかまった。互換性の問題についていくつか議論があったものの、最終的には libbpf への移行が実現した。

これに伴い、Map の定義方法が少し変化しているので注意

Note: Users should use new BTF way to defined the maps, the examples in legacy folder which is using struct bpf_elf_map defined maps is not recommanded.
https://github.com/shemminger/iproute2/tree/main/examples/bpf

// 古い方法
struct bpf_elf_map __section_maps map_sh = {
	.type		= BPF_MAP_TYPE_ARRAY,
	.size_key	= sizeof(uint32_t),
	.size_value	= sizeof(uint32_t),
	.pinning	= PIN_OBJECT_NS, /* or PIN_GLOBAL_NS, or PIN_NONE */
	.max_elem	= 1,
};

// libbpf 流の書き方
struct {
	__uint(type, BPF_MAP_TYPE_ARRAY);
	__uint(key_size, sizeof(uint32_t));
	__uint(value_size, sizeof(uint32_t));
	__uint(max_entries, 1);
	__uint(pinning, LIBBPF_PIN_BY_NAME);	/* or LIBBPF_PIN_NONE */
} map_sh __section(".maps");
OpenJNYOpenJNY

__uintbpf/bpf_helpers.h に、BPF_MAP_TYPE_ARRAY 等の定数は linux/bpf.h に定義されてる

OpenJNYOpenJNY

libbpf: BTF is required, but is missing or corrupted.

事象

$ sudo tc filter add dev eth0 ingress bpf da obj tail-call.o sec classifier
libbpf: BTF is required, but is missing or corrupted.
ERROR: opening BPF object file failed
Unable to load program

原因: BPF Type Format (BTF) が付与されていない ELF を libbpf で読み込もうとしたこと

https://atmarkit.itmedia.co.jp/ait/articles/2008/04/news004.html
https://stackoverflow.com/questions/58914021/libbpf-error-loading-elf-section-btf-0

対応: clang-g オプションを付与する

$ clang -g -O2 -Wall -target bpf -c prog.c -o prog.o
OpenJNYOpenJNY

tc コマンドあれこれ

tc qdisc add dev $dev clsact
tc qdisc del dev $dev clsact
tc qdisc show dev $dev

# "return -1" を実行する BPF プログラムを任意パケットに合致するフィルターとして使う (flowid 1:1 をアサインしてドロップする)
tc filter add dev $dev [ingress|egress] bpf bytecode '1,6 0 0 4294967295,' flowid 1:1 action drop

# egress 方向の 8.8.8.8 にあてた SYN パケットをドロップ
tcpdump -ilo -ddd '(tcp[tcpflags] & tcp-syn != 0) and (host 8.8.8.8)' | tr '\n' ',' > /tmp/bytecode
tc filter add dev $dev egress bpf bytecode-file /tmp/bytecode action drop

# action として BPF プログラムを読みこむ (direct-action [da] モード)
# da モード時の返却値が持つ意味の例:
# - TC_ACT_UNSPEC (-1): tc に設定されたデフォルトアクションを実施
# - TC_ACT_OK (0): パケットの許可 (パケット処理のパイプラインが終了)
# - TC_ACT_RECLASSIFY (1): パケット処理のパイプラインをはじめからやり直す
# - TC_ACT_SHOT (2): パケットのドロップ (パケット処理のパイプラインが終了)
# - TC_ACT_PIPE (3): パイプラインの続きを実行する
cat <<EOF > /tmp/bpf.c
#include <linux/bpf.h>
#include <linux/pkt_cls.h>
#include <bpf/bpf_helpers.h>

SEC("foo")
int foo_main(struct __sk_buff *skb) {
    return TC_ACT_OK;
}

char __license[] SEC("license") = "GPL";
EOF
clang -O2 -Wall -g -target bpf -c /tmp/bpf.c -o /tmp/bpf.o
tc filter add dev $dev ingress bpf [direct-action|da] [object-file|obj] /tmp/bpf.o sec foo [verb]