Linux flow offload提高路由转发效率

凡是正确的东西,该来的最终还是会来的。 (当然了,经理可能也有同感。)

来看看几年前我写的文章:

很多年前我写了很多这种模块,各种协议栈短路的设计,并且在上海的很多机房里跑了很久,也用这些东西成功面试了很多家大公司,也用这些东西面试过很多应聘者…

一晃好多年过去了,优化conntrack并置入e1000e驱动的周末恍如昨日,如今,Linux 4.18内核之后,使用标准内核,路由转发终于可以正儿八经地被offload了:

  • 系统在网卡ingress建立flow cache,flow元组命中的将会直接被转发,不必再进行路由查找。

关于flow offload的详细信息参见下面的文章:
https://lwn.net/Articles/738214/

其原理图如下:
在这里插入图片描述
你觉得flow offload仅仅就是在Netfilter上增加了个HOOK吗?如果这样,我早就能做到了,而且在上海市的很多机房跑了好多年了。

flow offload真正厉害的地方是它迎合了云计算虚拟化智能网卡offload网络处理逻辑的风潮,实现了Linux标准的Netfilter API的硬件offload接口!它的意义在于下图显示的:
在这里插入图片描述
再来总结一下数据包的路径:

  • 网卡收包进ingress。
  • ingress匹配flow table:
    • 命中:
      1. 获取flow entry项里的路由项。
      2. 解析路由项里的出口网卡dev_out,MAC地址等。
      3. 递减TTL。
      4. 直接dev_out转发。
    • 未命中:
      1. 转交给慢速路径,标准Linux内核处理。
      2. ip_forward在FORWARD hook点将flow entry注入flow table(软件或硬件)。

迄至Linux 5.3版本的内核,硬件offload依然处于pending状态,关于硬件offload的patch,可以参考下面链接的patch:
https://www.mail-archive.com/netdev@vger.kernel.org/msg198158.html
https://www.mail-archive.com/netdev@vger.kernel.org/msg198155.html
https://www.mail-archive.com/netdev@vger.kernel.org/msg198157.html
https://www.mail-archive.com/netdev@vger.kernel.org/msg198156.html
https://www.mail-archive.com/netdev@vger.kernel.org/msg198159.html

本质上来讲,硬件offload其实就是调用了一个网卡驱动程序的接口,实现操作网卡流表的目的。


在演示怎么玩之前,必须澄清一个问题,即flow offload和nf_conntrack的关系。

首先,flow offload依赖两个内核模块:

  1. nf_flow_table
  2. nft_flow_offload

当modinfo它们时,一个很令人遗憾的消息出现了:

root@zhaoya-VirtualBox:~/# modinfo nft_flow_offload |grep depends
depends:        nf_tables,nf_flow_table,nf_conntrack

它们依赖nf_conntrack这么一个饱受诟病的东西。在我五六年前实现conntrack rtcache的时候,我还特意优化了nf_conntrack,增加了一个hot cache机制,从而提升了性能,当时我还使用的是2.6.32这个古老的版本。

虽然一直到5.3版本,nf_conntrack一直在迭代优化,但是它本质上还是避免不了lock,依然不能被彻底洗白,遗憾的是,flow offload依赖了nf_conntrack。

那么flow offload的性能收益会不会因为nf_conntrack被打折扣呢?也许吧。

但是,这里要从三方面看这件事:

  1. 承认性能损失,毕竟nf_conntrack也带来了很多功能性收益。
  2. 修改flow offload,接触对nf_conntrack的依赖,同时也丧失了与NAT等功能的联动机制。
  3. 向前看,flow offload的主场在硬件offload,Netfilter的软flow offload就是个Demo,谁来在乎nf_conntrack呢。

是时候演示怎么玩了。

搭建下面的拓扑:
在这里插入图片描述
我们希望的效果是在中间R机器上配置flow offload,从A访问B的流量将被offload,不再需要经过R机器的路由查找。

登录到中间的R机器,打开ip_forward,我是用nftables来配置flow offload的,编写以下的nft配置:

# bypass.conf
flush ruleset
table ip filter {
	flowtable ft {
		hook ingress priority 0;
		devices = {enp0s9, enp0s10};
	}
	chain forward {
		type filter hook forward priority 0;
		ip protocol tcp flow offload @ft
	}
}

注意⚠️:本文不讲nft,请自行翻阅其手册,非常好玩。

执行nft命令加载它:

root@zhaoya-VirtualBox:~/monitor# nft -f ./bypass.conf
root@zhaoya-VirtualBox:~/monitor# nft list flowtables
table ip filter {
	flowtable f {
		hook ingress priority filter
		devices = { enp0s9, enp0s10 }
	}
}
root@zhaoya-VirtualBox:~/monitor#

同时,为了验证流量确实被offload了,我们只需要证明一个flow除了第一个包之外的后续流量没有经过FORWARD链即可,我们添加一条iptables空规则,然后查看确认它的统计数据:

root@zhaoya-VirtualBox:~/monitor# iptables -L FORWARD -v
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0            all  --  any    any     anywhere             anywhere
root@zhaoya-VirtualBox:~/monitor#

此时,在机器B上发起到A的ssh连接并随便执行个命令产生数据输出,也就是模拟一个双向通信的TCP流:


查看iptables的统计数据:

root@zhaoya-VirtualBox:~/monitor# iptables -L FORWARD -v
Chain FORWARD (policy ACCEPT 2 packets, 120 bytes)
 pkts bytes target     prot opt in     out     source               destination
    2   120            all  --  any    any     anywhere             anywhere
root@zhaoya-VirtualBox:~/monitor#

可见,仅仅有2个包通过了FORWARD链。后续的包均被offload到了网卡的ingress。

那么,为什么是2个包触发了flow offload呢?如果确实是2个包触发flow offload,具体是哪两个包呢?

以下是触发flow offload项被创建添加进flow table的代码:

static void nft_flow_offload_eval(const struct nft_expr *expr,
                                  struct nft_regs *regs,
                                  const struct nft_pktinfo *pkt)
{
	...
	// 必须关联一个被confirm的conntrack才可被视为一条完整的可offload的流
	if (!nf_ct_is_confirmed(ct)) 
		goto out;
	...
	flow = flow_offload_alloc(ct, &route);
	...
	ret = flow_offload_add(flowtable, flow);

可见,可offload的flow的条件是其conntrack必须被confirm,即 完整地经过Netfiler路径的flow才可被offload ,这是合理的,毕竟被当前Box给DROP掉的流不是完整的流,offload它没有任何意义。

因此,一个TCP流的双向包均被FORWARD链 “看到” 是该流被offload的必要条件,所以,我们在iptables FORWARD的统计数据中看到了2个包。

现在,我们已经知道,主机B发起的经由主机R到达主机A的TCP流已经被主机R的flow offload机制给卸载了,如果我们在主机R上DROP掉FORWARD数据包,会怎样呢?

让我们试一下:

root@zhaoya-VirtualBox:~/monitor# iptables -F
root@zhaoya-VirtualBox:~/monitor# iptables -A FORWARD -j DROP
root@zhaoya-VirtualBox:~/monitor#
root@zhaoya-VirtualBox:~/monitor# tcpdump -i enp0s9 -n
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on enp0s9, link-type EN10MB (Ethernet), capture size 262144 bytes
18:42:17.775319 IP 192.168.57.7.22 > 192.168.58.7.60987: Flags [P.], ...
18:42:17.775734 IP 192.168.58.7.60987 > 192.168.57.7.22: Flags [.], ...
18:42:18.775997 IP 192.168.57.7.22 > 192.168.58.7.60987: Flags [P.], ...
18:42:18.776370 IP 192.168.58.7.60987 > 192.168.57.7.22: Flags [.], ...

依然可以看到双向的TCP包被转发,这说明了两个事实:

  1. 能抓到包说明我们用的flow offload是基于Netfilter的软实现。
  2. 没有被iptables的FORWARD DROP规则丢包说明iptables没有同步给flowtable。

此时,我们flush掉nftables的flow offload规则,再抓包:

root@zhaoya-VirtualBox:~/monitor# nft flush ruleset
root@zhaoya-VirtualBox:~/monitor# tcpdump -i enp0s9 -n
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on enp0s9, link-type EN10MB (Ethernet), capture size 262144 bytes
18:46:23.304633 IP 192.168.57.7.22 > 192.168.58.7.60987: Flags [P.], ...
18:46:25.035613 IP 192.168.57.7.22 > 192.168.58.7.60987: Flags [P.], ...
18:46:28.502334 IP 192.168.57.7.22 > 192.168.58.7.60987: Flags [P.], ...
18:46:35.427412 IP 192.168.57.7.22 > 192.168.58.7.60987: Flags [P.], ...
^C

很明显,双向通信被iptables的DROP规则阻滞了,只剩下TCP重传了,看前面的重传间隔时间戳,符合TCP的RTO退避规则。


完成了一个简单的验证实验之后,我们是不是要祈求一个iptables的target呢?这对于大多数熟悉iptables却不熟悉nftables的我们十分有意义,比如如下设置规则即可实现以上类似的flow offload:

iptables -A FORWARD -p tcp -j FLOWOFFLOAD

嗯,有擦掌欲试的想法了,这对我而言并不难,曾经对xtables-addons各种高级玩。

实现这个target的过程顺便还可以把flow offload对nf_conntrack的那个依赖砍掉,实现一个 更加纯粹(不带那些个花里胡哨的NAT支持) 的flow offload机制,换句话说,我将自己定义flowtable结构体,除了元组信息之外,去除其它花里胡哨的东西。

然而,似乎没有必要,因为OpenWRT已经实现了一个xt_FLOWOFFLOAD target了:

netfilter: add a xt_FLOWOFFLOAD target for NAT/routing offload support

This makes it possible to add an iptables rule that offloads routing/NAT
packet processing to a software fast path. This fast path is much
quicker than running packets through the regular tables/chains.

Requires Linux 4.14

Signed-off-by: Felix Fietkau nbd@nbd.name

代码来自其github:
https://github.com/openwrt/openwrt/commit/820f03099894bd48638fb5be326b5c551f0f2b98

似乎我也就不需要再做什么了…

插曲:
有人问我写那么多模块和路由转发优化的文章,为什么却不给社区提交patch呢?很简单,因为我对Linux内核社区这个熟人名利场没有兴趣,整这些东西可以提高自己的知名度,也会极大的削弱兴趣以及降低手艺人(而不是工程师)的效率,规范和协议是为了多人协作的意义存在的,一个人干手艺活儿当然是怎么方便怎么来了,我完全不需要关注一行多少字符,也可以随便命名函数和变量,当然引入很多魔术字也很方便。不过,经理可能不这么想。

此外:

  1. 这些模块是给公司写的。
  2. 这些思路都比较简单,没什么技术含量,整理成套话会比代码好些。

说回flow offload,我觉得整体来看,这里有三方面的内容:

  1. 对于转发的包,offload其路由的查找过程,即在FORWARD链cache它的路由项到flowtable。
  2. 对于本地处理的包,offload其socket的查找过程,即在传输层cache它的socket地址到flowtable。
  3. 上述1和2,如果网卡支持(可编程SmartNIC),那么将cache置于硬件,即HW offload。

说来说去,还是我几年前想做的那些事,只是一直没有条件玩智能网卡,因为太贵吧。当然,经理并不一定这么认为。


浙江温州皮鞋湿,下雨进水不会胖。

OVS offload 是指在开放式虚拟交换机(Open vSwitch,即OVS)中使用硬件加速来提升网络数据包处理的性能和效率。传统上,虚拟交换机在软件层面进行数据包处理,这可能会导致性能瓶颈和延迟增加。因此,为了解决这些问题,OVS offload 技术应运而生。 通过 OVS offload,虚拟交换机可以将一些网络数据包的处理任务委托给硬件设备来完成,而不是完全依赖于软件。这些硬件设备可以是物理网络交换机的芯片或网卡上的功能块,也可以是专门设计的网络加速卡(Network Interface Card,即NIC),具体取决于硬件厂商的支持。 OVS offload 技术带来了多方面的好处。首先,它可以大幅度提高网络数据包的处理速度和吞吐量,从而减少延迟并提供更好的网络性能。其次,它可以减轻CPU的负担,使其能够处理更多的网络流量和更复杂的网络任务。此外,OVS offload 还可以提供更好的网络流量监控和安全性,通过硬件加速可以更快速地检测和处理网络攻击。 然而,OVS offload 技术也存在一些限制。首先,它取决于硬件设备的支持,因此只有特定的硬件设备才能充分发挥其优势。其次,OVS offload 目前仍处于发展阶段,可能存在一些兼容性问题或性能优化的空间。因此,在实际应用中,需要仔细评估硬件设备的支持和兼容性,以及进行适当的性能测试和调优。 总的来说,OVS offload 技术为虚拟交换机提供了一种有效的性能优化手段,可以提高网络数据包处理的效率和性能。它在实际应用中具有广泛的应用前景,并且随着硬件技术的不断发展,其性能还将进一步提升。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值