再谈UDP GSO和GRO

张开发
2026/4/18 21:04:06 15 分钟阅读

分享文章

再谈UDP GSO和GRO
shixudong163.com一、UDP/GSO再认识近期本人收到一位网友私信“tx-udp-segmentation on时发送UDP报文, 发包端抓包发现协议栈仍然分片这是为什么”在回答这个问题之前先简要梳理一下UDP/GSO和TCP/GSO的主要区别1、对于TCP只要开启TSOGSO总是开启不受GSO开关控制只有TSO关闭时才能通过GSO开关控制GSO开启与否。然而自内核v4.17起GSO总是开启无论TSO开启与否再也不受GSO开关影响。2、对于UDP没有对应的GSO开关并且对网络应用不透明需要应用程序显式启用UDP/GSO。虽然UDP/GSO也有类似TSO的硬件卸载开关tx-udp-segmentation但也需要应用程序显式启用后才能发挥tx-udp-segmentation的作用。所以针对开头的问题实际上就是传统的UDP应用程序根本无法利用UDP/GSO功能包括硬件卸载tx-udp-segmentation。在《也谈UDP GSO和GRO》一文中提到由于内核v3.7调整了抓包位置导致无法通过抓包工具验证TCP/GSO效果。然而对于UDP来说在应用程序没有显式启用UDP/GSO时内核只能采用IP分片方式拆分UDP大包。应用程序显式启用UDP/GSO后内核将使用TSO/GSO同样的机制采用UDP分段方式拆分UDP大包因此UDP/GSO的直观效果可以通过抓包工具验证。受《TPROXY与Wireguard》文末小插曲的启发可以利用管道串接dd和socat构造UDP大包再通过socat的setsockopt参数启用UDP/GSO。网卡不支持tx-udp-segmentation时使用dd if/dev/zero bs1024 count8 |socat -b65536 - UDP-send:192.168.4.68:8888,setsockopt17:103:1472可以抓到UDP分段包除最后一包外每包长度1514。网卡支持tx-udp-segmentation时使用前述命令可以抓到长度为82348*10242814的UDP大包。如不带setsockopt参数则无论网卡是否支持tx-udp-segmentation使用dd if/dev/zero bs1024 count8 |socat -b65536 - UDP-send:192.168.4.68:8888只能抓到IP分片包Fragmented IP。UDP/GSO硬件卸载特性tx-udp-segmentation自内核v4.18引入支持该特性的逻辑网卡在《也谈UDP GSO和GRO》一文有提到包括bridgev4.18起支持、内核wireguardv5.11起支持等。自内核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/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*14481*151426130字节的大包。内核v3.13起GRO已支持frags和frag_list组合使用最多可合并17*1448frags27*1448frag_list此处frag_list可以继续嵌套frags1*151465226字节的大包合并效率较早期内核GRO大大提高。然而该效率提高却并不完全适用于转发环节虽然转发环节不对前述GRO大包做拆分与合并动作但由于硬件网卡基本上都不支持tx-scatter-gather-fraglist导致带有frag_list属性的大包包含其frags部分在继续转发出去前只能由GSO重新拆分为小包或不超过2468217*1448322014字节的纯frags大包。对于长度大于26130字节的组合方式大包显然浪费了不必要的GRO合并和GSO拆分开销。通过抓包也能发现入口网卡能收到最大65226字节的大包而转发网卡最大只能发送纯frags方式26130字节的大包vnetX和veth网卡的TSO支持透明传输GSO大包因此接收网卡无需使用组合方式收包此时转发网卡最大可以发送纯frags方式65226字节的大包。针对这一不足自内核v6.4起引入了CONFIG_MAX_SKB_FRAGS取值17和45之间默认还是17修改需要重新编译内核MAX_SKB_FRAGS取值45后内核GRO合并时无需使用frag_list也能合并44*1448frags1*151465226字节的大包转发时再也无需经由GSO重新拆分小包显著提高了转发效率。对于UDP来说目前有三种方法可以接收UDP/GRO包1、应用程序显式启用相应的socket选项接收UDP/GRO内核v5.02、入口网卡开启rx-gro-list接收和转发UDP/GRO内核v5.63、入口网卡开启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_listUDP/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引入的rx-gro-list开关和合并函数skb_gro_receive_list。通过设置入口网卡rx-gro-liston改用TCP fraglist GRO后仅适用于转发本机接收仍使用传统的组合方式在转发网卡不支持TSO特性时可有效改善转发效率。根据源码和官方资料如果同时开启方法2和方法3方法2优先在转发网卡支持tx-udp-segmentation时考虑到方法2转发效率不如方法3正确的操作方式应该是只开启方法3。方法1只能用于本机接收GRO底层框架和方法3一致合并函数为skb_gro_receive方法2和3既能用于转发也能用于本机接收。上述三种方法用于本机接收时考虑到纯flag_list方式涉及到更多的skb申请/释放操作相对来说方法1和方法3的效率更佳。然而方法3有一个小瑕疵只要本机存在UDP隧道方法3就只能用于转发而无法用于本机接收。三、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协议栈后内核UDP协议还需要将GRO大包重新拆分为小包然后再逐包提交上层应用以免上层应用接收缓冲区溢出。这种情形下GRO大包经过的途径较短两头还需要先合并再拆分优化效率有限而当上层应用显式启用UDP/GRO后内核UDP协议能直接向上层应用提交GRO大包。其实引入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实现时的小瑕疵无法用于存在UDP隧道情形下的本机接收而wg网卡也属于UDP隧道对于承载上层wg网卡数据的底层UDP包来说属于本机接收性质因此底层网卡只能使用rx-gro-list。但由于内核wireguard本身并不支持对底层UDP数据包的GRO合并通过rx-gro-list合并的底层UDP/GRO大包后续仍然需要重新拆分为小包后再提交wireguard驱动处理因此针对wireguard底层网卡启用UDP/GRO的意义不大。wg网卡本身的UDP/GRO设置则取决于本机接收还是后续转发本机接收也只能使用rx-gro-list如wg网卡接收的UDP包主要用于转发建议使用rx-udp-gro-forwarding后续转发时效率要高于使用rx-gro-list详见前面分析过程。至于最新版用户空间wireguard-go如内核版本已为v6.2wireguard-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设置对于底层网卡来说已显式启用对于wg网卡来说要么彻底不支持要么提供透明支持相应开关无意义。自v6.2内核起wireguard-go用作入口网卡时还有一桩好处即wg网卡为上层应用提供透明的UDP/GRO实现时并没有依托内核而是直接在wg网卡驱动位于wireguard-go用户空间自行实现。如前所述内核GRO采用frags方式收包时每个frags只能容纳一个帧对于长度超过26130字节的GRO大包内核自动采用frags和frag_list组合方式收包。而wg网卡驱动只使用纯frags方式进行GRO合并收包单个frags可以容纳多个帧纯frags方式最大可以合并65226字节的大包因此将wireguard-go用作入口网卡时转发网卡只需支持tx-udp-segmentation即可转发GRO大包无需支持tx-scatter-gather-fraglist等其他特性。顺便提一下即使v6.2内核起wireguard-go自行实现了wg网卡对上层应用透明的UDP/GRO支持也就是说既无需上层应用显式启用UDP/GRO也无需wg网卡启用rx-gro-list和rx-udp-gro-forwarding实际上是根本不会发生内核GRO调用然而在使用wg网卡进行本机接收时内核UDP协议同样需要将GRO大包重新拆分为小包然后逐包提交上层应用。而若上层应用显式启用了UDP/GRO则同时也向上层应用放行了GRO大包提交功能针对wg网卡虽然仍不会涉及内核GRO调用但内核UDP协议也能直接向上层应用提交GRO大包。四、关于tx-gso-list和UDP/GRO转发v5.6引入的UDP fraglist GRO/GSO同时新增了两个ethtool开关即rx-gro-list和tx-gso-listrx-gro-list已经在《也谈UDP GSO和GRO》和本文做了详细分析。至于tx-gso-list在《也谈UDP GSO和GRO》中也有提到如入口网卡采用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/UDP GSO只使用frags方式发送数据所以用不到网卡的tx-scatter-gather-fraglist特性事实上目前似乎也没有物理网卡支持该特性。然而在转发环节取决于入口网卡GRO合并大包所采用方式转发数据可能包含frags方式或frag_list方式、以及两者的组合。如转发数据为纯frags方式对应rx-udp-gro-forwardingon且MAX_SKB_FRAGS45转发网卡只需支持tx-udp-segmentation即可如转发数据为两者组合方式对应rx-udp-gro-forwardingon转发网卡需增加第二个特性tx-scatter-gather-fraglist如转发数据为纯frag_list方式对应rx-gro-liston转发网卡还需再增加第三个特性tx-gso-list。UDP/GRO引入的tx-gso-list开关和rx-gro-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属性的逻辑网卡如bridge才支持tx-gso-list和tx-udp-segmentationv5.11通过更新NETIF_F_GSO_SOFTWARE定义使得大部分逻辑网卡如wireguard等也开始支持tx-gso-list和tx-udp-segmentation。然而对于bridge/bond等设备来说是否具备NETIF_F_GSO_SOFTWARE特性完全取决于下挂网卡。由于1、支持tx-udp-segmentation的硬件网卡依然很少2、鉴于tx-gso-list本质上属于内核特性与硬件完全无关将其定义为NETIF_F_GSO_SOFTWARE硬件特性而非NETIF_F_SOFT_FEATURES或NETIF_F_SOFT_FEATURES_OFF内核特性使得物理网卡不可能支持tx-gso-list。NETIF_F_GSO_SOFTWARE特性的重定义使得bridge/bond在下挂物理网卡时bridge/bond网卡本身将不再支持tx-gso-list并且大部分情形下也无法支持tx-udp-segmentation。v6.2起虽然virtio_net、tun也开始支持tx-udp-segmentation然而却仍不支持tx-gso-list。显然由于内核将tx-gso-list错误定义为NETIF_F_GSO_SOFTWARE硬件特性使得真实物理网卡不可能支持tx-gso-list导致将物理网卡用作转发网卡时纯frag_list方式下UDP/GRO大包只能由GSO重新拆分为小包后再转发转发性能明显低于其他两种方式。至于v6.10引入的TCP fraglist GRO因为直接借用了UDP/GRO的rx-gro-list和tx-gso-list也会存在上述tx-gso-list的短板问题必然引起TCP/GRO和UDP/GRO之间的冲突。自内核v6.10起TCP/GRO也采用该机制后即使是为了实现UDP/GRO而开启rx-gro-list只要转发网卡不支持tx-gso-list或tx-scatter-gather-fraglistTCP/GRO大包也必须先由GSO重新拆分为小包后再转发严重影响了TCP/GRO大包的转发效率。而为了保障TCP/GRO大包的转发效率就不能开启rx-gro-list从而也无法使用rx-gro-list接收UDP/GRO大包。以上冲突事实上使得rx-gro-list成为了鸡肋甚至是TCP/GRO大包的毒药。TCP fraglist GRO功能同样可以通过抓包观察效果转发网卡需开启TSO入口网卡不启用rx-gro-listoff时采用组合方式收包转发网卡上能抓到TCP/GRO大包物理网卡因为不支持tx-scatter-gather-fraglist最大包长26130字节逻辑网卡如支持scatter-gather-fraglist最大包长可为65226字节。入口网卡启用rx-gro-liston时采用纯fraglist方式收包如前所述由于物理网卡不可能支持tx-gso-list逻辑网卡如不能同时支持tx-gso-list和tx-scatter-gather-fraglist转发网卡只能抓到GSO分段后的TCP小包。在进行以上抓包测试时入口网卡不能选择主机vnetX或虚拟机virtio_net。virtio_net具备rx_gro_hw特性无需启用内核GRO就能收取GSO大包在virtio_net上即使启用rx-gro-liston也因为透传的GSO大包无需合并不会转换成纯frag_list方式。主机上的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特性。五、UDP GSO/GRO和veth如前所述作为逻辑网卡自内核v5.11起veth网卡支持并默认开启tx-udp-segmentation和tx-gso-list同时veth网卡还支持tx-scatter-gather-fraglist因此veth作为转发网卡可适配各种情形下的UDP/GRO大包转发。在内核v5.13之前veth作为入口网卡时如没有启用XDP就只能使用非NAPI驱动netif_rx收包导致veth网卡压根不会发生内核GRO调用。内核v5.13针对该情形做了优化veth网卡默认关闭GRO手动开启GRO开关sudo ethtool -K vethX gro on并启用rx-gro-list或rx-udp-gro-forwarding后就可以通过NAPI驱动收包确保veth网卡能顺利调用内核GRO收包。遗憾的是上述优化还不够完美即使上层应用显式启用了UDP/GRO功能仍需要手动开启veth入口网卡的rx-gro-list或rx-udp-gro-forwarding以触发NAPI驱动发挥作用否则依旧采用netif_rx方式收包不会发生内核GRO调用。六、UDP GSO/GRO和VxLANOverlay网络如VxLAN针对上层网络的TCP/GSO只要底层网卡支持TSO和tx-udp_tnl-segmentation/tx-udp_tnl-csum-segmentation底层网卡就能硬件卸载或透明传输VxLAN封装后的UDP大包。至于上层网络的TCP/GRO同样完全依赖于内核实现虽然底层采用了UDP封装但内核也早在v4.18/v5.0之前就已实现了底层UDP大包的合并。对于上层网络的UDP/GSO鉴于自内核v5.11起VxLAN网卡支持并默认开启tx-udp-segmentation上层应用显式启用UDP/GSO后同样只要底层网卡支持tx-udp-segmentation和tx-udp_tnl-segmentation/tx-udp_tnl-csum-segmentation底层网卡就能硬件卸载或透明传输VxLAN封装后的UDP大包。如内核版本在v4.18和v5.11之间由于VxLAN本身不支持tx-udp-segmentation上层应用显式启用UDP/GSO后发送的UDP/GSO大包在离开VxLAN网卡前就被内核GSO拆分为UDP分段包此时即使底层网卡支持tx-udp-segmentation和tx-udp_tnl-segmentation/tx-udp_tnl-csum-segmentation也无从实现硬件卸载或透明传输底层UDP大包。对于上层网络的UDP/GRO在v5.13之前由于内核尚没有对此类UDP-tunnel GRO进行优化如使用rx-gro-list开关启用底层网卡的UDP/GRO功能将直接对外层UDP进行合并如上层应用显式启用了UDP/GRO功能则第一时间对内层UDP进行合并。这两种情形下的GRO大包后续都将在外层对应的udp_queue_rcv_skb函数里被重新拆分为小包然后再提交给上层VxLAN驱动处理。已拆分的内层数据包到达VxLAN网卡后将根据是否显式启用UDP/GRO功能或VxLAN网卡的UDP/GRO开关再次进行必要的GRO合并操作。自v5.13起内核针对UDP-tunnel GRO进行了同步优化只需启用底层网卡的rx-gro-list开关或者上层应用显式启用UDP/GRO功能均可在第一时间对内层UDP进行合并并且合并后的GRO大包无需被外层udp_queue_rcv_skb函数重新拆分小包而是直接提交给上层VxLAN驱动处理。该情形下内层数据包到达VxLAN网卡时已经是GRO大包因此在底层网卡启用UDP/GRO开关后VxLAN网卡相应的UDP/GRO开关就失去了作用。至于本地接收的内层GRO大包除非上层应用显式启用了UDP/GRO功能否则总是被内层对应的udp_queue_rcv_skb函数重新拆分为小包然后再逐包提交上层应用。鉴于VxLAN网卡也属于UDP隧道故同样无法将rx-udp-gro-forwarding用于本地接收。因此具体的UDP/GRO设置取决于本机接收还是后续转发如以本机接收为主仅需开启底层网卡的rx-gro-list如以转发为主则建议仅开启VxLAN网卡的rx-udp-gro-forwarding此时底层网卡不能开启rx-gro-list后续转发时效率要高于使用rx-gro-list。当VxLAN网卡用作转发网卡时由于其同时支持tx-udp-segmentation、tx-scatter-gather-fraglist和tx-gso-list可适配各种情形下的UDP/GRO大包转发。另外虽然ethtool显示bridge网卡已支持tx-udp_tnl-segmentation但由于内核bridge代码缺失对hw_enc_features的赋值导致该特性后续无法通过netif_skb_features确认使得bridge网卡事实上无法支持tx-udp_tnl-segmentation。此时即使bridge下挂网卡支持tx-udp_tnl-segmentation和TSO/tx-udp-segmentation但由于封装TCP/UDP GSO的底层UDP大包在离开bridge网卡前已被内核GSO拆分为UDP分段包从而无法使用下挂网卡硬件卸载或透明传输底层UDP大包的能力。该情形下建议改用支持tx-udp_tnl-segmentation和TSO/tx-udp-segmentation的ovs网桥替换内核bridge网桥。

更多文章