原副标题:eBPF 控制技术课堂教学:加速罐子互联网转贴,费时减少60%+
译者 | 王栋栋
背 景
Linux 具有机能强大的互联网协议栈,因此兼具了十分杰出的操控性。但是,这是相对的。纯粹从互联网协议栈各模块的视角来说,的确努力做到了机能与操控性的均衡。不过,当把数个模块女团起来,去满足用户前述的销售业务市场需求,机能与操控性的天授就会下压。
罐子互联网就是十分众所周知的范例,晚期的罐子互联网,借助 bridge、netfilter + iptables (或 lvs)、veth 等模块的女团,同时实现了基本的互联网转贴;不过,操控性却不令人满意。原因也比较明晰:受制于彼时的控制技术发展情况,为的是满足用户报文在不同互联网 namespace 之间的转发,彼时能优先选择的计划多于 bridge + veth 女团;为的是同时实现 POD 提供服务项目、出访 NODE 以外的互联网等市场需求,能优先选择的计划多于 netfilter + iptables(或 lvs)。这些女团的控制技术计划增加了更多的互联网转贴费时,故在操控性上有了更多的耗损。
不过,eBPF 控制技术的出现,抹杀了这一切。eBPF 控制技术增添的Mach电子电路能力,能在旧有艰难转贴方向上,锻造一些“虫洞”,让数据流加速抵达出发地。特别针对罐子互联网的情景,他们能借助 eBPF,略去 bridge、netfilter 等模块,加速数据流转贴。
上面他们以罐子互联网为情景,用前述数据做支撑力,剖析 eBPF 加速罐子互联网转贴的基本原理。
互联网拓扑
如图,三台电子设备 Node-A/B 透过 eth1 洛佐韦,VLAN为 192.168.1.0/24。 Node-A/B 中依次建立罐子 Pod-A/B,罐子存储电子设备名叫 ve0,是 veth 电子设备,VLAN为 172.17.0.0/16。 Node-A/B 中依次建立桥USB br0,VLAN为 172.17.0.0/16,透过 lxc0(veth 电子设备)与 Pod-A/B 连通。 在 Node、Pod 互联网 namespace 中,依次增设动态路由器;其中,Pod 中动态路由器交换机为 br0,Node 中动态路由器交换机为对端 Node USB门牌号。 为的是方便快捷试验与分析,他们将 eth1 的存储电子设备堆栈增设为 1,因此将存储电子设备受阻存取到 CPU0。# ethtool -L eth1 combined 1 # echo 0 > /proc/irq/$(cat /proc/interrupts | awk -F : /eth1/ {gsub(/ /,””); print $1})/smp_affinity_listbridge
bridge + veth 是罐子互联网最早的转贴模式,他们结合上面的互联网拓扑,分析一下互联网报文的转贴方向。
在上面互联网拓扑中,eth1 收到出发门牌号为 172.17.0.0/16 VLAN的数据流,会经过路由器查找,走到 br0 的发包流程。 br0 的发包流程,会根据 FDB 表查找目的 MAC 门牌号归属的子USB,如果没有查找到,就洪泛(遍历所有子USB,发送数据流);否则,优先选择特定子USB,发送数据流。在本例中,会优先选择 lxc0 USB,发送数据流。 lxc0 口是 veth 口,Mach的同时实现是 veth 口发包,对端(peer)的 veth 口就会收包。在本例中,Pod-A/B 中的 ve0 口会收到数据流。 至此,完成收包方向的主要流程。 当数据流从 Pod-A/B 中发出,会先在 Pod 的互联网 namespace 中查找路由器,假设流量从 Pod-A 发往 Pod-B,那么会命中他们之前增设的动态路由器:172.17.0.200 via 172.17.0.1 dev ve0,最终数据流会从 ve0 口发出,目的 MAC 门牌号为 Node-A 上面 br0 的门牌号。 ve0 口是 veth 口,和收包方向类似,对端的 veth 口 lxc0 会收到数据流。 lxc0 口是 br0 的子USB,由于数据流目的 MAC 门牌号为 br0 的USB门牌号,数据流会经过 br0 口上送到 3 层协议栈处理。 3 层协议栈会查找路由器,命中他们之前增设的动态路由器:172.17.0.200 via 192.168.1.20 dev eth1,最终数据流会从 eth1 口发出,发给 Node-B。 至此,完成发包方向的主要流程。上面的流程比较抽象,他们用 perf ftrace 能十分直观地看到数据流都经过了哪些Mach协议栈方向。
收包方向
# perf ftrace -C0 -G __netif_receive_skb_list_core -g smp_* 如图,收包方向主要经历路由器查找、桥转贴、veth 转贴、veth 收包等阶段,中间多次经过 netfilter 的 hook 点。 最终调用 enqueue_to_backlog 函数,报文暂存到每个 CPU 私有的 input_pkt_queue 中,一次软受阻结束,总费时 79us。 但是数据流并没有抵达终点,后续软受阻到来时,会有机会调用 process_backlog,处理每个 CPU 私有的 input_pkt_queue,将数据流丢入 Pod 互联网 namespace 的协议栈继续处理,直到将数据流送往 socket 的堆栈,才算是抵达了终点。 综上,收包方向要消耗 2 个软受阻,才能将数据流送达终点。发包方向
# perf ftrace -C0 -G __netif_receive_skb_core -g smp_* 如图,发包方向主要经历 veth 收包、桥上送、路由器查找、物理存储电子设备转贴等阶段,中间多次经过 netfilter 的 hook 点 。 最终调用存储电子设备驱动发包函数,一次软受阻结束,总费时 62us。分析
由 perf ftrace 的结果能看出,借助 bridge + veth 的转贴模式,会多次经历 netfilter、路由器等模块,过程十分冗长,导致了转贴操控性的下降。
他们接下来看一下,如何用 eBPF 跳过非必须的流程,加速互联网转贴。
首先,他们先看一下Mach协议栈主要支持的 eBPF hook 点,在这些 hook 点他们能注入 eBPF 程序,同时实现具体的销售业务市场需求。
他们能看到,与网络转贴相关的 hook 点主要有 XDP(eXpress Data Path)、TC(Traffic Control)、LWT(Light Weight Tunnel)等。
特别针对于罐子互联网转贴的情景,比。而 LWT 则比较靠上层,数据流抵达这个 hook 点,会经过很多模块(如:netfilter)。
加速收包方向
如图,在 eth1 的 TC hook 点(收包方向)挂载 eBPF 程序。
# tc qdisc add dev eth1 clsact # tc filter add dev eth1 ingress bpf da obj ingress_redirect.o sec classifier-redirecteBPF 程序如下所示,其中 lxc0 USB的 index 为 2。bpf_redirect 函数为Mach提供的 helper 函数,该函数会将 eth1 收到的报文,直接转贴至 lxc0 USB。
SEC(“classifier-redirect”) int cls_redirect(struct __sk_buff *skb) { /* The ifindex of lxc0 is 2 */ return bpf_redirect(2, 0); }加速发包方向
如图,在 lxc0 的 TC hook 点(收包方向)挂载 eBPF 程序。
# tc qdisc add dev lxc0 clsact # tc filter add dev lxc0 ingress bpf da obj egress_redirect.o sec classifier-redirecteBPF 程序如下所示,其中 eth1 USB的 index 为 1。bpf_redirect 函数会将 lxc0 收到的报文,直接转贴至 eth1 USB。
SEC(“classifier-redirect”) int cls_redirect(struct __sk_buff *skb) { /* The ifindex of eth1 is 1 */ return bpf_redirect(1, 0); }分析
由上面的操作能看到,他们直接跳过了 bridge 的转贴,借助 eBPF 程序,将 eth1 与 lxc0 之间建立了一个加速转贴通路。上面他们用 perf ftrace 看一下加速效果。
收包方向
# perf ftrace -C0 -G __netif_receive_skb_list_core -g smp_*如图,在收包方向的 TC 模块中,由 bpf_redirect 函数增设转贴信息( lxc0 USB index),由 skb_do_redirect 函数直接调用了 lxc0 USB的 veth_xmit 函数;略去了路由器、bridge、netfilter 等模块。
最终调用 enqueue_to_backlog 函数,报文暂存到每个 CPU 私有的 input_pkt_queue 中,一次软受阻结束,总费时 43us;比 bridge 转贴模式的 79us,费时减少约 45%。
但是,收包方向仍然要消耗 2 个软受阻,才能将数据流送达终点。
发包方向
如图,在发包方向的 TC 模块中,由 bpf_redirect 函数增设转贴信息( eth1 USB index ),由 skb_do_redirect 函数直接调用了 eth1 USB的 xmit 函数;略去了路由器、bridge、netfilter 等模块。
最终调用存储电子设备驱动发包函数,一次软受阻结束,总 费时 36us,相比 bridge 模式 62us,费时减少了约 42%。
小结
由 perf ftrace 的结果能看出,借助 eBPF 在 TC 模块注入转贴逻辑,能跳过Mach协议栈非必须的流程,同时实现加速转贴。 收发两个方向的费时依次减少 40% 左右,操控性提升十分可观。
但是,他们在收包方向上面仍然需要消耗 2 个软受阻,才能将数据流送往出发地。接下来他们看,如何借助 redirect peer 控制技术来优化这个流程。
TC redirect peer
加速收包方向
如图,在 eth1 的 TC hook 点(收包方向)挂载 eBPF 程序。
# tc qdisc add dev eth1 clsact # tc filter add dev eth1 ingress bpf da obj ingress_redirect_peer.o sec classifier-redirecteBPF 程序如下所示,其中 lxc0 USB的 index 为 2。bpf_redirect_peer 函数为Mach提供的 helper 函数,该函数会将 eth1 收到的报文,直接转贴至 lxc0 USB的 peer USB,即 ve0 USB。
SEC(“classifier-redirect”) int cls_redirect(struct __sk_buff *skb) { /* The ifindex of lxc0 is 2 */ return bpf_redirect_peer(2, 0); }分析
由于 bpf_redirect_peer 会直接将报文转贴到 Pod 互联网 namespace 中,避免了 enqueue_to_backlog 操作,节省了一次软受阻,操控性理论上会有提升。他们用 perf ftrace 验证一下。
# perf ftrace -C0 -G __netif_receive_skb_list_core -g smp_*如图,在收包方向的 TC 模块中,由 bpf_redirect_peer 函数增设转贴信息( lxc0 USB index),由 skb_do_redirect 函数调用 veth_peer_dev 查找 lxc0 的 peer USB,增设 skb->dev = ve0,返回 EAGAIN 给 tcf_classify 函数。
tcf_classify 函数会判断 skb_do_redirect 的返回值,如果是 EAGAIN,则触发 __netif_receive_skb_core 函数伪递归调用(透过 goto 同时实现)。这样,就十分巧妙地同时实现了互联网 namespace 的切换(在一次软受阻上下文中)。
最终,透过 tcp_v4_rcv 函数抵达数据流的终点,整个转贴流程费时 75us。从上面的函数费时能看到,ip_list_rcv 函数相当于 Pod 互联网 namespace 的费时,本文描述的 3 种转贴模式,这段转贴方向是相同的。所以,将 ip_list_rcv 函数费时减去,转贴费时约为 14us(这里还忽略了 2 次软受阻调度的时间)。比 TC redirect 模式的 43us、bridge 模式的 79us,转贴费时依次减少为 67%、82%。
总 结
本文以罐子互联网为例,对比了 3 种罐子互联网转贴模式的操控性差异。透过 perf ftrace 的函数调用关系以及费时情况,详细分析了导致操控性差异的原因。他们演示了仅仅透过几行 eBPF 代码,就能大大缩短数据流转贴方向,加速Mach互联网转贴的效率,互联网转贴费时最多可减少 82%。
目前 eBPF 控制技术在开源社区十分流行,在 tracing、安全、互联网等领域有广泛应用,他们能借助这项控制技术做很多有意思的事情。感兴趣的朋友能加入他们,一起讨论交流。
译者简介
王栋栋,字节跳动系统控制技术与工程团队Mach工程师,10 年系统工程师工作经验,关注 Linux networking、eBPF 等领域。目前在字节跳动,主要负责 eBPF、Mach互联网协议栈相关的开发工作。
每天中午都是一次“秒杀”,从 IT 视角看麦当劳中国数字化
对话iPod之父:这不是互联网最坏的年代
“羊了个羊”背后公司清仓式分红10亿元;Meta元宇宙部门今年已亏94亿美元;微软称GitHub年收入10亿美元|Q资讯
全面审查Twitter代码、当场炒掉CEO等众多高管:马斯克正式入主Twitter