再谈UDP GSO和GRO
GSO发包只使用frags,但GRO收包默认使用frags和frag_list组合(除非明确只使用纯frag_list),GRO要使用纯frags收包,必须修改MAX_SKB_FRAGS为45
shixudong@163.com
一、UDP/GSO再认识
近期本人收到一位网友私信:“tx-udp-segmentation = on时,发送UDP报文, 发包端抓包发现协议栈仍然分片,这是为什么?”在回答这个问题之前,先简要梳理一下UDP/GSO和TCP/GSO的主要区别:
1、对于TCP,只要开启TSO,GSO总是开启(不受GSO开关控制),只有TSO关闭时,才能通过GSO开关控制GSO开启与否。然而,自内核v4.17起,GSO总是开启,无论TSO开启与否,再也不受GSO开关影响。
2、对于UDP,没有对应的GSO开关,并且对网络应用不透明,需要应用程序设置相应的socket选项显式启用。UDP有类似TSO的硬件卸载开关tx-udp-segmentation,但也需要应用程序显式启用UDP GSO特性后才能发挥tx-udp-segmentation的作用。
所以针对开头的问题,实际上就是传统的UDP应用程序根本无法利用UDP/GSO特性(包括硬件卸载tx-udp-segmentation)。在《也谈UDP GSO和GRO》一文中提到,由于内核v3.7调整了抓包位置,导致无法通过抓包工具验证TCP/GSO效果。但对于UDP/GSO来说,虽然其同样使用了TSO/GSO的底层框架和机制,然而由于UDP/GSO在传输层对网络应用不透明的特性,UDP/GSO的直观效果是可以通过抓包工具验证的。
受到《TPROXY与Wireguard》文末小插曲的启发,可以利用管道串接dd和socat构造UDP大包,再通过socat的setsockopt参数启用UDP/GSO。网卡不支持tx-udp-segmentation时,使用dd if=/dev/zero bs=1024 count=8 |socat -b65536 - UDP-send:192.168.4.68:8888,setsockopt=17:103:1472,可以抓到UDP分段包(除最后一包外每包长度1514)。网卡支持tx-udp-segmentation时,使用前述命令可以抓到长度为8234(8*1024+28+14)的UDP大包。如不带setsockopt参数,则无论网卡是否支持tx-udp-segmentation,使用dd if=/dev/zero bs=1024 count=8 |socat -b65536 - UDP-send:192.168.4.68:8888,只能抓到IP分片包(Fragmented IP)。
UDP/GSO硬件卸载特性tx-udp-segmentation自内核v4.18引入,支持该特性的逻辑网卡在《也谈UDP GSO和GRO》一文有提到,包括内核wireguard、bridge/bond/team等。自内核v6.2起, virtio_net、tun也开始支持tx-udp-segmentation,与前面那些逻辑网卡不同的是,虚拟机virtio_net支持tx-udp-segmentation特性不仅需要主机tun驱动的配合,还需要QEMU8.0的加持,所以guest和host内核都需要升级到v6.2。此外低版本QEMU升级到QEMU8.0后,还需要额外修改xml文件使得guest的机器类型和QEMU8.0一致。前述逻辑/虚拟网卡中,virtio_net需要手工开启tx-udp-segmentation,其他网卡则默认开启。
二、UDP/GRO再认识
由于当时对网卡的两个特性tx-scatter-gather和tx-scatter-gather-fraglist认识不够深入,导致《也谈UDP GSO和GRO》一文中,对GRO大包转发出口处的抓包分析不够全面。经过对Linux有关GRO卸载技术源代码的再学习,对GRO相关底层实现有了进一步的理解,特记录于此。
tx-scatter-gather和tx-scatter-gather-fraglist可以分别对应到内核sk_buff的frags数组和frag_list 链表。在本机发送环节,TCP/GSO和UDP/GSO只使用frags方式发送数据,且网卡必须支持tx-scatter-gather特性(事实上目前基本上所有网卡都已支持tx-scatter-gather);未启用UDP/GSO时,本机发送的UDP大包由udp_sendmsg调用__ip_make_skb转换成frag_list链表,并在IP层被ip_do_fragment拆分成IP分片包(实际上此处发送环节的frag_list链表与tx-scatter-gather-fraglist没有任何关系,在此处产生frag_list 链表,仅仅是为了后续能够被ip_do_fragment快速拆分而已)。在网卡接收环节,基本上所有硬件网卡也都是使用frags方式接收数据,个别硬件网卡使用frag_list方式接收数据,然后两者都交给内核GRO做进一步的合并处理。
在内核TCP/GRO处理环节,早期内核GRO不支持frags和frag_list组合使用,由于大部分网卡只支持使用frags方式收包,且frags数组最多只能包含MAX_SKB_FRAGS(内核硬编码,取值17)个成员,而且收包不像发包,每个frags只能容纳一个帧(1448字节),导致GRO最多只能合并17*1448+1*1514=26130字节的大包。内核v3.13起GRO已支持frags和frag_list组合使用,最多可合并17*1448(frags)+27*1448(frag_list)+1*1514=65226字节的大包,合并效率较早期内核GRO大大提高。然而该效率提高却并不完全适用于转发环节,虽然转发环节在IP层不对前述大包做拆分与合并动作,但由于硬件网卡基本上都不支持tx-scatter-gather-fraglist,导致带有frag_list属性的大包(包含其frags部分)在继续转发出去前只能由GSO重新拆分为小包。对于长度大于26130字节的组合方式大包,显然浪费了不必要的GRO合并和GSO拆分开销。通过抓包也能发现,入口网卡能收到最大65226字节的大包,而转发网卡最大只能发送纯frags方式的26130字节的大包。针对这一不足,自内核v6.4起,引入了CONFIG_MAX_SKB_FRAGS(取值17和45之间,默认还是17,修改需要重新编译内核),MAX_SKB_FRAGS取值45后,内核GRO合并时无需使用frag_list也能合并44*1448(frags)+1*1514=65226字节的大包,转发时再也无需经由GSO重新拆分小包,显著提高了转发效率。
对于UDP来说,目前有三种方法可以接收UDP/GRO包:
1、应用程序显式启用相应的socket选项接收UDP/GRO(内核v5.0);
2、开启rx-gro-list接收和转发UDP/GRO(内核v5.6);
3、开启rx-udp-gro-forwarding接收和转发UDP/GRO(内核v5.12)。
方法3接收和转发UDP/GRO时,采用了TCP/GRO同样的底层框架(合并函数都是skb_gro_receive),存在TCP/GRO同样的问题,即UDP/GRO合并后有部分大包同时采用了frags和frag_list组合。如转发网卡不支持tx-udp-segmentation,包括纯frags方式在内的所有大包都将由GSO重新拆分为小包,显著降低了转发效率。如转发网卡支持tx-udp-segmentation,则只有frag_list属性的大包(包含其frags部分)才需要由GSO重新拆分为小包,相较前者,还能适度平衡转发效率。
方法2接收和转发UDP/GRO时,GRO底层引入了新的合并函数skb_gro_receive_list,UDP/GRO合并大包采用纯frag_list方式,即使转发网卡支持tx-udp-segmentation,但依然不会支持tx-scatter-gather-fraglist,所有大包都将由GSO重新拆分为小包。
显然在转发网卡支持tx-udp-segmentation时,方法3总体转发效率要优于方法2。在转发网卡不支持tx-udp-segmentation时,由于方法2后续GSO拆分frag_list大包较方法3同时拆分frags/frag_list组合方式大包效率有改善,此时方法2转发效率要优于方法3。
基于同样的原因,对于TCP来说,如果转发网卡不支持TSO(如PPPOE、wifi),鉴于先前TCP/GRO不支持纯frag_list方式合并大包,导致后续TCP/GSO拆分frags/frag_list组合方式GRO大包时效率较低。针对这一不足,自内核v6.10起,新增了纯frag_list方式的TCP fraglist GRO support,底层共用UDP/GRO引入的合并函数skb_gro_receive_list。在转发网卡不支持TSO特性时,通过设置入口网卡rx-gro-list=on改用TCP fraglist GRO(仅适用于转发,本机接收仍使用传统的组合方式),可有效改善转发效率。
根据源码和官方资料,如果同时开启方法2和方法3,方法2优先,在转发网卡支持tx-udp-segmentation时,考虑到方法2转发效率不如方法3,正确的操作姿势应该是只开启方法3。
方法1只能用于本机接收,GRO底层框架和方法3一致(合并函数为skb_gro_receive),方法2和3既能用于转发,也能用于本机接收。上述三种方法用于本机接收时,考虑到纯flag_list方式涉及到更多的skb申请/释放操作,相对来说,方法1和方法3的效率更佳。然而方法3有一个小瑕疵,本机接收无法用于内核udp隧道的底层UDP,比如内核wireguard使用的底层UDP。
三、rx-gro-list和rx-udp-gro-forwarding再认识
如前所述,rx-gro-list和rx-udp-gro-forwarding可同时用于本机接收和转发,用于本机接收时,优势是上层应用无需显式启用UDP/GRO功能,但不足也很明显,应用程序在没有显式启用UDP/GRO功能时,实际上不知道自身能够接收GRO大包,接收缓冲区一般仅和普通UDP包长度相匹配。因此将rx-gro-list和rx-udp-gro-forwarding用于本机接收时,GRO大包到达本机UDP socket后,内核UDP协议还需要将GRO大包重新拆分为小包,然后逐包加入socket接收队列,以免上层应用接收缓冲区溢出。这种情形下,GRO大包经过的途径较短,两头还需要先合并再拆分,优化效率有限,而当上层应用显式启用UDP/GRO后,内核UDP协议能直接将GRO大包加入socket接收队列。其实引入rx-gro-list和rx-udp-gro-forwarding的初衷是为了提高转发效率,转发环节不涉及上层应用,通过入口网卡这两个开关和转发网卡的tx-udp-segmentation等相关特性配合,理想情况下,UDP GRO大包可以从入口网卡到转发网卡一路畅通无阻,最终由转发网卡硬件完成卸载工作,显然可有效提高转发效率。
在《也谈UDP GSO和GRO》一文中提到,将wireguard网卡用于转发网卡时,为了充分利用其tx-udp-segmentation特性,入口网卡应只启用rx-udp-gro-forwarding。本文则针对wireguard网卡用作入口网卡进行展开分析。
将内核wireguard网卡用作入口网卡接收UDP/GRO大包时,建议同时开启wg网卡以及底层网卡的UDP/GRO,分别对应上下两层UDP。由于rx-udp-gro-forwarding实现时的小瑕疵,本机接收无法用于wg网卡的底层UDP,因此底层网卡只能使用rx-gro-list。wg网卡本身建议使用rx-udp-gro-forwarding,无论后续转发还是自己接收,效率都要高于使用rx-gro-list(详见前面分析过程)。
至于最新版用户空间wireguard-go,如内核版本已为v6.2,wireguard-go能显式启用底层网卡的UDP/GRO,同时为wg网卡上层应用提供透明的UDP/GRO支持;如内核版本在v5.0和v6.2之间,则仅显式启用底层网卡的UDP/GRO,此时wg网卡无法为上层应用提供UDP/GRO,即使上层应用显式启用也无效,这是由于wireguard-go压根就没有考虑为wg网卡驱动提供GRO调用机制,相关GRO开关对wg网卡也没有实际意义。因此将wireguard-go用作入口网卡时,无需考虑底层网卡和wg网卡的rx-gro-list和rx-udp-gro-forwarding开关设置(前者已显式启用,后者无意义)。
自v6.2内核起,wireguard-go用作入口网卡时还有一桩好处,即wg网卡为上层应用提供透明的UDP/GRO实现时,并没有依托内核,而是直接在网卡驱动层面自行实现,wg网卡GRO使用纯frags方式收包,每个frags可以容纳多个帧。而内核GRO采用frags方式收包时,每个frags只能容纳一个帧。如前所述,对于长度超过26130字节的GRO大包,内核必须采用frags和frag_list组合方式收包。因此wireguard-go作为入口网卡和转发网卡配合提供转发服务时,只需转发网卡支持tx-udp-segmentation特性即可转发GRO大包,无需支持tx-scatter-gather-fraglist等其他特性。
四、关于tx-gso-list和UDP GRO转发
v5.6引入的UDP fraglist GRO/GSO同时新增了两个ethtool开关,即rx-gro-list和tx-gso-list,rx-gro-list已经在《也谈UDP GSO和GRO》和本文做了详细的分析。至于tx-gso-list,在前一篇文章中提到,如入口网卡采用rx-udp-gro-forwarding,转发网卡只要支持tx-udp-segmentation即可卸载或透明转发UDP大包(实际上转发网卡还需要同时支持tx-scatter-gather,只不过因为基本上所有网卡都已具备该特性,默认就不提这一茬了);如入口网卡采用rx-gro-list,转发网卡需要同时支持tx-udp-segmentation、tx-gso-list和tx-scatter-gather-fraglist三种特性,才能卸载或透明转发UDP大包。
前面分析过,本机发送环节,TCP/GSO和UDP/GSO只使用frags方式发送数据,所以用不到网卡的tx-scatter-gather-fraglist特性,事实上目前似乎也没有物理网卡支持该特性。然而在转发环节,取决于入口网卡GRO合并大包所采用方式,转发数据可能包含frags方式或frag_list方式、以及两者的组合。如转发数据为纯frags方式(对应rx-udp-gro-forwarding=on且MAX_SKB_FRAGS=45),转发网卡只需支持tx-udp-segmentation即可;如转发数据为两者组合方式(对应rx-udp-gro-forwarding=on),转发网卡需增加第二个特性tx-scatter-gather-fraglist;如转发数据为纯frag_list方式(对应rx-gro-list=on),转发网卡还需再增加第三个特性tx-gso-list。
UDP/GRO引入的tx-gso-list特性,实际上与硬件无关,其初衷是为了在转发情形下可以独立控制是否由GSO对纯frag_list方式的GRO大包进行软件分段,然而由于纯frag_list方式GRO大包还要受转发网卡tx-scatter-gather-fraglist特性影响,导致tx-gso-list特性不仅显得有点多余,并且增加了纯frag_list方式下UDP/GRO转发的复杂性。
自v5.6引入tx-gso-list特性以来,直到v5.10,只有设置了NETIF_F_GSO_MASK属性的逻辑网卡才支持tx-gso-list和tx-udp-segmentation特性(如bridge/bond/team)。v5.11通过更新NETIF_F_GSO_SOFTWARE定义,使得大部分逻辑网卡也开始支持tx-gso-list和tx-udp-segmentation特性,如wireguard。然而对于bridge/bond/team等设备来说,是否具备NETIF_F_GSO_SOFTWARE特性,完全依赖于下挂网卡。由于:1、支持tx-udp-segmentation特性的硬件网卡依然很少,2、到目前为止,已支持tx-udp-segmentation特性的硬件网卡都不支持tx-gso-list特性。NETIF_F_GSO_SOFTWARE特性的重定义,导致事实上bridge/bond/team不再支持tx-gso-list,并且大部分情形下也不再支持tx-udp-segmentation。v6.2起,虽然virtio_net、tun开始支持tx-udp-segmentation,然而却仍不支持tx-gso-list。
显然,由于网卡(包含逻辑网卡)普遍缺乏对tx-gso-list特性的支持,至少在逻辑网卡层面,纯frag_list方式下UDP GRO大包的转发性能明显低于其他两种方式。
至于v6.10引入的TCP fraglist GRO,因为直接借用了UDP/GRO的rx-gro-list和tx-gso-list特性,也会存在上述tx-gso-list的短板问题。不过由于TCP fraglist GRO的引入,本意就是为了在个别网卡不支持TSO时改善转发效率,实际环境中转发网卡已经不支持TSO,即使支持tx-gso-list也毫无意义,TCP/GRO大包总是需要经由GSO重新拆分。如前所述,纯frag_list方式接收TCP/GRO,只是为了后续转发时改善GSO的拆包效率而已。
v6.10新增的TCP fraglist GRO功能,可以通过抓包观察效果(转发网卡需开启TSO),入口网卡不启用rx-gro-list(off)时,转发网卡上能抓到TCP/GRO大包(物理网卡因为不支持tx-scatter-gather-fraglist,最大包长26130字节;逻辑网卡如支持scatter-gather-fraglist,最大包长可为65226字节)。入口网卡启用rx-gro-list=on时,如前所述,由于物理网卡都不支持tx-scatter-gather-fraglist特性,逻辑网卡不会同时支持tx-scatter-gather-fraglist和tx-gso-list特性,转发网卡只能抓到GSO分段后的小包。
在进行以上抓包测试时,入口网卡请不要选择主机vnetX或虚拟机virtio_net。virtio_net具备rx_gro_hw特性,无需启用内核GRO就能收取GSO大包,在virtio_net上即使启用rx-gro-list=on,也因为透传的GSO大包无需合并,不会转换成纯frag_list方式。主机上的vnetX,其底层为tun驱动,vnetX也能直接收取虚拟机发出的GSO大包,并且其压根没有GRO功能一说。这两种情形下,TCP fraglist GRO功能针对这些GSO大包无法发挥作用,透传的纯frags方式GSO大包将不受转发网卡tx-scatter-gather-fraglist和tx-gso-list特性影响,转发网卡上仍能抓到TCP/GSO大包,从而无法通过抓包观察TCP fraglist GRO效果。如非要选择virtio_net作为入口网卡进行抓包测试,可关闭其rx_gro_hw特性,其实质是同步关闭主机上对应vnetX网卡的TSO功能。
 
 为武汉地区的开发者提供学习、交流和合作的平台。社区聚集了众多技术爱好者和专业人士,涵盖了多个领域,包括人工智能、大数据、云计算、区块链等。社区定期举办技术分享、培训和活动,为开发者提供更多的学习和交流机会。
更多推荐



所有评论(0)