#1209 tc-bpf ======================== 第一个 tc-bpf 程序 --------------------- 程序: .. code-block:: c #include <linux/bpf.h> #define SEC(x) __attribute__((section(x), used)) SEC("mycls") int cls_main(struct __sk_buff *skb) { return 2; // TC_ACT_SHOT } char __license[] __section("license") = "GPL"; - ``TC_ACT_*`` 宏定义:https://github.com/torvalds/linux/blob/v5.9/tools/include/uapi/linux/pkt_cls.h#L32 - ``__sk_buff`` 结构体定义:https://github.com/torvalds/linux/blob/v5.9/tools/include/uapi/linux/bpf.h#L3718 保存代码为文件 ``foo.c``,然后编译: .. code-block:: console # clang -O2 -target bpf -c foo.c -o foo.o 加载运行/查看/删除: .. code-block:: console # tc qdisc add dev eth0 clsact # tc qdisc show dev eth0 qdisc fq_codel 0: root refcnt 2 limit 10240p flows 1024 quantum 1514 target 5.0ms interval 100.0ms memory_limit 32Mb ecn qdisc clsact ffff: parent ffff:fff1 # tc filter add dev eth0 ingress bpf da obj foo.o sec mycls # tc filter add dev eth0 egress bpf da obj foo.o sec mycls # tc filter show dev eth0 ingress filter protocol all pref 49152 bpf chain 0 filter protocol all pref 49152 bpf chain 0 handle 0x1 foo.o:[mycls] direct-action not_in_hw id 17 tag 3b185187f1855c4c jited # tc filter show dev eth0 egress filter protocol all pref 49152 bpf chain 0 filter protocol all pref 49152 bpf chain 0 handle 0x1 foo.o:[mycls] direct-action not_in_hw id 18 tag 3b185187f1855c4c jited # tc filter del dev eth0 ingress pref 49152 # tc filter del dev eth0 egress pref 49152 上面的程序运行后会 drop 所有的包。 参考: - https://lwn.net/Articles/671458/ - https://man7.org/linux/man-pages/man8/tc-bpf.8.html - https://archive.fosdem.org/2016/schedule/event/ebpf/attachments/slides/1159/export/events/attachments/ebpf/slides/1159/ebpf.pdf - https://qmonnet.github.io/whirl-offload/2020/04/11/tc-bpf-direct-action/ - https://docs.cilium.io/en/latest/bpf/#tc-traffic-control tc 在网络栈中的位置 -------------------- .. image:: images/tc-overview.png tc 程序的执行点位于系统网络栈的最底层,在 NIC Driver 之后,最关键的是在 tcpdump 之后,所以 tcpdump 又可以使用了 ✌️。 完整的图参见:https://commons.wikimedia.org/wiki/File:Netfilter-packet-flow.svg 使用 tc-bpf 来实现带宽控制 ------------------------------- tc-bpf 只能用来控制哪些流需要进行带宽控制,哪些不需要,实际的带宽控制需要结合 tc 的其他功能模块来完成。一般有以下 2 中方式。 方法一:结合 HTB 队列 ````````````````````````` .. image:: images/tc-bpf-htb.png **Hierarchical Token Bucket (HTB) 多层令牌桶** 是 tc 中常用的一种用于控制带宽的队列算法。 .. code-block:: bash tc qdisc add dev eth0 root handle 1: htb tc class add dev eth0 parent 1: classid 1:1 htb rate 10kbit tc class add dev eth0 parent 1: classid 1:2 htb rate 10mbit 在 bpf 中,通过返回限速的 classid 可以将流量分配给不同的带宽控制类别去。tc 命令行中一般使用 ``MAJOR:MINOR`` 这样的格式来表示 classid,MAJOR、MINOR 都是十六进制的。在代码中 classid 是 ``u32`` 类型的,转换方法: ``classid = MAJOR<<16 | MINOR`` 。 https://github.com/shemminger/iproute2/blob/v6.4.0/tc/tc_util.c#L89 .. code-block:: c SEC("mycls") int cls_bpf_prog(struct __sk_buff *skb) { if (...) { return 0x10001; // classid 1:1 } else if (...) { return 0x10002; // classid 1:2 } return 0; } 挂载 tc cls_bpf 程序: .. code-block:: bash tc filter add dev eth0 bpf obj cls_bpf.o sec mycls cls_bpf、act_bpf、clsact 是三个不同的 tc-bpf 挂载点。cls_bpf 通过返回不同的 classid 来分类包,act_bpf 通过返回 action code( ``TC_ACT_OK``、 ``TC_ACT_DROP`` 等)来处理包。这两个加载点是老的挂载点。所以加载的时候和前文第一个 tc-bpf 程序加载方式不一样,不需要加 ``da`` direct-action 参数。关于两者的差别可以参见:http://arthurchiao.art/blog/understanding-tc-da-mode-zh/ clsact 可以同时完成 cls_bpf 和 act_bpf 的功能,返回值返回 action code,还可以通过传入的上下文参数 ``skb->tc_classid`` 字段来改变 classid(但是这个方式没有调通,不知道为啥,わかりませんでした)。 https://juejin.cn/post/7256795117674233912 方法二:结合 FQ 队列 ``````````````````````````` .. image:: images/tc-bpf-edt.png **Fair Queue(FQ) 公平队列**,这个方法是 google 在 https://netdevconf.info/0x14/pub/papers/55/0x14-paper55-talk-paper.pdf 这个论文里提到的。这个方法中 bpf 程序通过上下文参数中的 ``skb->tstamp`` 设置 skb 的 EDT( Earliest Departure Time),FQ 模块会读取这个时间来调度包的发送。 .. code-block:: c SEC("myedt") int cls_bpf_prog1(struct __sk_buff *skb) { // 将发送包的时间延后 1 秒 skb->tstamp = bpf_ktime_get_ns() + 1000000000; return TC_ACT_OK; } 挂载 tc clsact bpf 程序和 FQ 模块: .. code-block:: bash tc qdisc add dev eth0 root fq tc qdisc add dev eth0 clsact tc filter add dev eth0 ingress bpf da obj cls_bpf.o sec myedt tc filter add dev eth0 egress bpf da obj cls_bpf.o sec myedt 这个方法比方法一的性能要好,但是实现稍微复杂点,上面的 bpf 程序只是展示了这种控制的原理,实际应用中 bpf 需要自行通过算法来计算 EDT。 内核中自带的一个比较完整的示例: - https://github.com/torvalds/linux/blob/master/tools/testing/selftests/bpf/progs/test_tc_edt.c - https://github.com/torvalds/linux/blob/master/tools/testing/selftests/bpf/test_tc_edt.sh