近日,Netfilter与Mellanox联合开发了flowtable硬件卸载功能,使flowtable成为标准的conntrack卸载方案,实现Linux标准Netfilter硬件卸载接口。作为一项新功能,还存在一些缺陷和不完善之处。对此,我们做了大量的硬件卸载开发工作,修复问题,优化功能,帮助提升内核功能和性能,并计划将此功能合并到UCloud外网网关中,进一步提升系统性能和管理能力。优化后的性能飞跃首先,我们来看一组简单粗暴的数据。在所有的硬件offload开发工作完成后,我们基于flowtable的conntrackoffload从bps、pps、cpuusage等维度进行了一系列的性能对比测试。测试结果如下:非硬件卸载模式:①单流带宽测试为12GBps,消耗2个主机CPU;②多流小包测试1Mpps,消耗8个主机CPU;硬件卸载模式:①单流带宽测试为23GBps,占用主机CPU0;②多流小包测试为8Mpps,消耗主机CPU;可以看出,在硬件卸载模式下,bps和pps的性能都有明显提升。不仅CPU没有损失,性能还分别提升了1倍和8倍。但是,新的连接率cps并没有明显提升。分析原因是这个性能是由软件协议栈中的conntrack完成的,与硬件无关。以现有的水平,一旦这个特性应用到产品上,估计会带来巨大的性能飞跃。当然,性能的提升离不开每一个细微技术的打磨:我们分别对conntrackoffload和NAT功能进行了修复和优化,并在此基础上还与Netfilter和Mellanox合作开发了tunneloffload的新特性.接下来,我们详细谈谈我们所做的工作。Flowtableoffload产生的背景Linux内核在4.16版本引入了flowoffload功能,为IP转发提供了基于流的offload功能。当新连接完成原方向和反向的第一轮数据包时,在完成路由、防火墙和NAT工作后,处理反向第一数据包的forwardhook根据如下信息创建一个unloadableflow数据包路由和NAT。在接收网卡的入口挂钩上。后续数据包可以直接在接收ingresshook上转发,无需进入IP栈处理。该模式实现了对完成连接建立的conntrack软件的卸载,但目前flowtableoffload只支持TCP和UDP协议的卸载。图:Flowtableoffload示意图目前内核中对offload的标准硬件支持只有NetworkTC子系统的tcflower接口。_PabloNeiraAyuso_首先公开了tcflower对应的offload接口为networksubsystemflowoffload,然后在flowtableoffload中支持HWflowoffload接口。这样,流卸载接口就可以在驱动层得到充分统一的使用。_PaulBlakey_将flowtableoffload分配给TC\_SETUP\_FT域,在驱动层实现不同的子系统。基本功能全部完成后,查看commit代码发现flowtableHWoffload的建立没有指定正确的TC\_SETUP\_FT类型。对此,我们做了一个简单的修复,并向社区提交了补丁:①Netfilter:nf\_flow\_table\_offload:FixblocksetupasTC\_SETUP\_FTcmd(https://git.kernel.org/pub/sc...)②Netfilter:nf\_flow\_table\_offload:Fixblock\_cbtc\_setup\_typeasTC\_SETUP\_CLSFLOWER(https://git.kernel.org/pub/sc...)ConntrackOffload测试优化实践接下来我们对conntrackoffload卸载功能进行了实测,发现offload规则存在一些问题,进行了修复和优化。|测试复现创建虚拟机,配置虚拟机地址为10.0.0.75。宿主机创建user1的vrf,并将虚拟机对应的VF表示符mlx\_pf0vf0和PFmlx\_p0添加到用户vrf中。PF对等体连接的物理机地址为10.0.1.241。主机充当虚拟机和对等物理机之间的虚拟机路由器。创建防火墙规则,允许访问虚拟机的icmp和tcp5001端口,并将VFrepresentor端口和PF端口添加到流表中进行offload。打开netperf测试,10.0.1.241访问10.0.0.75:5001。发现可以成功建立连接,但是后面的带宽跑不起来。|问题是在宿主机上抓不到相应的包,说明卸载成功,在虚拟机中抓包显示收到和回复的包都是正确的。但是在对端物理机上抓包发现dst\_mac都是0,我们猜测应该是卸载规则有问题。为了验证结论,我们查看相关源码并分析:通过源码可以发现dst\_neigh是正确获取的,但是由于dst\_neigh\_lookup对于没有neighbor项neighbor的地址会新建一个,导致值为0。(因为刚刚部署了整个测试环境,还没有进行通信,所以两端都没有邻居。)仔细分析发送offloadrule的时机。原始方向的第一个SYN消息来自10.0.1.241->10.0。0.75:5001可以通过协议栈传递给虚拟机,虚拟机回复方向10.0.0.75:5001->10.0.1.241发送SYN+ACK报文。同时会在Host的netfilter转发表中建立offload规则并发送给hardware。此时链路的两个方向的报文都得到认证,conntrack项也建立起来,形成一个完整的转发规则。但是reply方向仍然没有ip\_dst10.0.1.241的邻居,因为协议栈上的报文还没有真正发送出去触发arp过程。对于申诉分析,由于flowtableoffload不支持icmp协议,我们先进行ping检测,让两端的neighboritem建立成功,然后继续测试。这时发现形式化的硬件卸载可以正常工作了。|提交补丁:针对conntrackoffload中neighbor失效导致卸载错误的情况,我们提交补丁进行修复。简单总结就是:获取无效邻居时不要卸载:Netfilter:nf\_flow\_table\_offload:checkthestatusofdst\_neigh(https://git.kernel.org/pub/sc...)NAT功能测试优化实践接下来,我们继续对flowtableoffload中的NAT功能项进行了实测,并对部分卸载规则进行了优化改进。|测试复现:创建虚拟机,配置虚拟机地址为10.0.0.75,外部地址为2.2.2.11。宿主机创建user1的vrf,并将虚拟机对应的VF表示符mlx\_pf0vf0和PFmlx\_p0添加到用户vrf中。PF对等体连接的物理机地址为1.1.1.7。Host充当虚拟机和对端物理机之间的虚拟nat网关。同样创建防火墙规则,允许访问虚拟机的icmp和tcp5001端口,并将VFrepresentor端口和PF端口添加到flowtable中进行offload。然后创建nat转发规则,dstip地址2.2.2.11dnat到10.0.0.75,srcip地址10.0.0.75snat到2.2.2.11。启动netperf测试,访问1.1.1.7到2.2.2.11:5001。发现可以成功建立连接,但是后续的带宽值很低。|问题定位:在Host上抓不到相应的信息,说明卸载成功。在虚拟机抓包的时候,发现收到的报文dstmac不对,值也是0(测试代码没有集成,无效邻居先不要卸载补丁)。由于原方向访问2.2.2.11:5001的SYN报文1.1.1.7转换为1.1.1.7到10.0.0.75:5001并成功发送给虚拟机,此时10.0.0.75对应的邻居存在,并且应该没有采集不到的情况。我们猜测应该是卸载规则有问题。查看源码分析:根据源码,在NAT场景下,tuple->dst\_cache原来方向的device确实是mlx\_pf0vf0,但是tuple->dst\_v4还是原来的2.2。2.11不是10.0.0.75。10.0.0.75应该通过remote\_tuple->src\_v4获取。问题解决后,我们再次启动netperf测试,1.1.1.7:32315访问2.2.2.11:5001。发现还是可以成功建立连接,但是带宽还是跑不起来。在虚拟机上抓包,发现收到的包dst端口异常为32315,即1.1.1.7:32315到10.0.0.75:32315。我们判断卸载规则还有其他问题,查看源码进行分析:可以看到在DNAT场景下,原来方向的dst端口会被改成回复方向的dstport,即32315,回复方向的dst端口将改为原方向的源端口。这个逻辑显然是错误的。SNAT下也有类似的转换问题。原来方向的srcport会变成reply方向的dst端口,reply方向的src端口会变成原来方向的source端口。例如10.0.0.75:32200访问1.1.1.75:5001,回复消息将从1.1.1.75:32200变为2.2.2.11:32200。因此,NAT模式下的portmangle存在问题。正确的逻辑应该是:DNAT:应该把原来方向的dst端口改成reply方向的src端口;回复方向的src端口应该改成原来方向的dst端口。SNAT:应将原来方向的src端口改成reply方向的dstprt;应将回复方向的dst端口更改为原始方向的src端口。|提交补丁:最后,针对NAT测试问题,我们也提交了相关补丁进行修复:①Netfilter:nf\_flow\_table\_offload:fixincorrectethernetdstaddress(https://git.kernel.org/pub/sc...)②Netfilter:nf\_flow\_table\_offload:修复natportmangle。(https://git.kernel.org/pub/sc...)新特性开发:SDN网络下的Tunneloffload发现flowtablehardwareoffload不支持tunnel设备。对于SDN网络,隧道是一个关键指标。最终我们在conntrackoffload和NAT功能的优化修复过程中得到启发。在新思维的推动下,我们与Netfilter维护者PabloNeiraAyuso和Mellanox驱动工程师PaulBlakey合作,共同开发了隧道卸载的新特性。下面详细介绍开发细节。1、在Flowtable上设置隧道设备,实现卸载。在tcflowersoftwarefilterrule中,所有tunnelmetamatch和tunneldecap动作都设置在tunnel设备的ingressqdisc上。元信息只能在隧道设备接收入口获取。所以tcfloweroffloadmode中的offloadrules也会按照这个逻辑发送给tunnel设备。但隧道设备是一个虚拟的逻辑设备,需要找到下层硬件设备进行设置和卸载——即使用间接阻塞的方式来实现。首先,驱动程序注册以监听隧道创建的事件。隧道创建时,驱动会为对应的隧道设备设置一个tcflower规则回调。隧道设备与硬件设备的关联是通过这种间接阻塞的方式实现的。|提交补丁:我们首先提交了系列(http://patchwork.ozlabs.org/c...)——支持流卸载中的indirectblock方法,主要包括以下补丁:①cls\_api:修改tc\_indr\_block\_ing\_cmd参数。②cls\_api:移除tcf\_block缓存③cls\_api:增加flow\_indr\_block\_call函数④flow\_offload:将tcindirectblock移动到flowoffload⑤flow\_offload:支持getAftermulti-subsystemblock上面系列介绍了,flowtable可以通过indirectblock卸载隧道设备。①Netfilter:flowtable:addnf\_flow\_table\_block\_offload\_init()(https://git.kernel.org/pub/sc...)②Netfilter:flowtable:addindrblocksetupsupport(https://git.kernel.org/pub/sc...)2.隧道信息匹配和Action设计完成第一步后,flowtable可以成功设置卸载规则到隧道设备,但是此时还有一个问题:隧道不能支持信息匹配和decap和encap操作。为了解决这个问题,首先我们想到了Linux内核在4.3版本引入的轻量级隧道Lightweighttunneling,它提供了一种通过route方法设置隧道属性的方法:在创建隧道设备时指定外部模式,并使用路由设置的轻量级隧道隧道通过tun设备发送数据包。Flowtable的规则设置也是基于路由信息完成的,沿着这个思路:也可以通过路由信息获取隧道信息。提交patch:我们在flowtable中提交了tunnelmatch和encap/decapaction的offloadpatches:①Netfilter:flowtable:addtunnelmatchoffloadsupport(https://git.kernel.org/pub/sc...)②Netfilter:flowtable:addtunnelencap/decapactionoffloadsupport(https://git.kernel.org/pub/sc...)3.mlx5esupportsIndirectblockinTC\_SETUP\_FT然后,我们发现了第三个问题:mlx5edriverTherulesofflowtableoffloadin被分配给TC\_SETUP\_FT域,但此驱动程序不支持间接块。提交补丁:发现这一点后,我们向社区提交了一个补丁来改进这个功能:①net/mlx5e:refactorindrsetupblock(https://git.kernel.org/pub/sc...)②net/mlx5e:addmlx5e\_rep\_indr\_setup\_ft\_cbsupport(https://git.kernel.org/pub/sc...)4.Tunneloffload功能验证完成以上开发步骤后,我们也按照上面测试思路进行了功能验证,确认了功能的完善性和准确性。|测试验证:创建虚拟机,配置虚拟机地址为10.0.0.75,外部地址为2.2.2.11。host创建user1的vrf和key1000的gretaptunneldevicetun1,将虚拟机对应的VFrepresentormlx\_pf0vf0和tun1添加到user1vrf中。PFmlx\_p0配置底层地址172.168.152.75/24。PFpeer连接物理机网卡配置地址172.168.152.208/24,创建gretaptunneltun,tun设备的remote为172.168.152.75,key为1000,地址设置为1.1。1.7.一、虚拟机与对端物理机tun设备通过隧道相互访问:接下来创建防火墙规则,允许访问虚拟机的icmp和tcp5001端口,并添加VFrepresentor端口和tun1隧道设备到流表进行卸载。最后创建NAT转发规则:dstip地址2.2.2.11dnat到10.0.0.75,srcip地址10.0.0.75snat到2.2.2.11。经过测试,最终确认各项功能完备,正常。至此,我们实现了SDN网络下隧道分流的能力。写在最后以上是本项目涉及的一些核心问题。我们将这段时间贡献的补丁整理成一个列表,希望能为开发者提供帮助。读者可以点击“阅读原文”阅读完整的补丁列表。FlowOffload开源技术为业界带来了革命性的思想,但在技术的更新迭代过程中,一些新特性在实际应用中也会出现稳定性和兼容性问题。作为开源技术的陪伴者,UCloud一直在积极探索和丰富开源技术的功能,助力提升开源技术的稳定性。作者简介:徐文,UCloud虚拟网络平台研发工程师,Linux内核网络活跃开发者,Netfilter流表卸载稳定维护者
