Traffic Controller (tc) に BPF をアタッチする時に知っておくべきこと
-
tc
は ingress/egress 両方のパスで BPF プログラムをアタッチ出来る。 -
アタッチ ポイントは classifier (filter とも呼ばれる) と action のいずれか
- filter にアタッチする場合は
BPF_PROG_TYPE_SCHED_CLS
- action にアタッチする場合は
BPF_PROG_TYPE_SCHED_ACT
- filter にアタッチする場合は
-
eBPF をアタッチするために作られた専用の qdisc
clsact
が存在する-
https://lwn.net/Articles/671458/
The clsact qdisc works on ingress, but also on egress.
In both cases, it's execution happens without taking the qdisc lock, and
the main difference for the egress part compared to prior version of [1]
is that this can be applied with any underlying real egress qdisc (also
classless ones). - アタッチ時に
direct-action
オプションを有効化することで、実際には action としてふるまう eBPF を filter としてアタッチすることができる$ tc qdisc add dev $DEV clsact $ tc filter add dev $DEV [ingress|egress] bpf [direct-action|da] obj "${PROG}.o" sec $SEC
-
https://lwn.net/Articles/671458/
-
https://qmonnet.github.io/whirl-offload/2020/04/11/tc-bpf-direct-action/
-
https://linuxjf.osdn.jp/JFdocs/Adv-Routing-HOWTO/lartc.qdisc.html
-
https://legacy.netdevconf.info/1.1/proceedings/slides/borkmann-tc-classifier-cls-bpf.pdf
XDP との関係
XDP の eBPF アタッチポイントは以下の 3 種類存在する。
- H/W Offloading され、NIC で実行する eBPF (NIC のサポートが必要)
- Driver 内部で実行される eBPF (Driver のサポートが必要)
- Driver で処理された直後に実行される eBPF (Generic XDP と呼ばれ、任意の NIC/ドライバで実行可能)
ポイント:
- XDP は ingress のみ対応
- ingress に関しては、TC filter よりも XDP の方が先に実行される
- XDP の eBPF プログラムは skb (sk_buff) になっていない生のパケットを処理するが、TC ではすでに skb のデータ構造で表現されたパケットを処理する
libbpf 対応の話
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");
__uint
は bpf/bpf_helpers.h
に、BPF_MAP_TYPE_ARRAY
等の定数は linux/bpf.h
に定義されてる
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 で読み込もうとしたこと
対応: clang
で -g
オプションを付与する
$ clang -g -O2 -Wall -target bpf -c prog.c -o prog.o
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]