校验和卸载

介绍

本文档描述了 Linux 网络堆栈中的一组技术,用于利用各种网卡的校验和卸载功能。

描述了以下技术

  • TX 校验和卸载

  • LCO:本地校验和卸载

  • RCO:远程校验和卸载

应该在此处记录但尚未记录的内容

  • RX 校验和卸载

  • CHECKSUM_UNNECESSARY 转换

TX 校验和卸载

在 include/linux/skbuff.h 顶部的注释中详细解释了将传输校验和卸载到设备的接口。

简而言之,它允许请求设备填写由 sk_buff 字段 skb->csum_start 和 skb->csum_offset 定义的单个 ones-complement 校验和。设备应计算从 csum_start 到数据包末尾的 16 位 ones-complement 校验和(即“IP 样式”校验和),并将结果填写到 (csum_start + csum_offset) 处。

由于 csum_offset 不能为负数,这确保了校验和字段的先前值包含在校验和计算中,因此它可以用于提供对校验和的任何必要校正(例如 UDP 或 TCP 的伪报头之和)。

此接口仅允许卸载单个校验和。如果使用封装,数据包可能在不同的报头层中具有多个校验和字段,其余的将必须由另一种机制(例如 LCO 或 RCO)处理。

也可以使用此接口卸载 CRC32c,方法是如上所述填写 skb->csum_start 和 skb->csum_offset,并设置 skb->csum_not_inet:有关更多详细信息,请参见 skbuff.h 注释(“D”部分)。

不执行 IP 报头校验和的卸载;它始终在软件中完成。这没问题,因为当我们构建 IP 报头时,我们显然将其缓存在缓存中,因此求和并不昂贵。它也相当短。

GSO 的要求更加复杂,因为在分割封装的数据包时,可能需要为每个生成的段编辑或重新计算内部和外部校验和。有关更多详细信息,请参见 skbuff.h 注释(“E”部分)。

驱动程序在 netdev->hw_features 中声明其卸载功能;更多信息请参阅 网络设备功能混乱以及如何从中脱身 。请注意,仅通告 NETIF_F_IP[V6]_CSUM 的设备仍必须遵守 SKB 中给出的 csum_start 和 csum_offset;如果它尝试在硬件中自行推断这些值(某些网卡会这样做),则驱动程序应检查 SKB 中的值是否与硬件将推断的值匹配,如果不匹配,则回退到在软件中进行校验和计算(使用 skb_csum_hwoffload_help() 或 skb_checksum_help() / skb_crc32c_csum_help 函数之一,如 include/linux/skbuff.h 中所述)。

在大多数情况下,堆栈应假定底层设备支持校验和卸载。唯一应该检查的地方是 validate_xmit_skb() 以及它直接或间接调用的函数。该函数比较 SKB 请求的卸载功能(可能包括除 TX 校验和卸载之外的其他卸载),如果设备不支持或未启用这些功能(由 netdev->features 确定),则在软件中执行相应的卸载。对于 TX 校验和卸载,这意味着调用 skb_csum_hwoffload_help(skb, features)。

LCO:本地校验和卸载

LCO 是一种在内部校验和将要被卸载时高效计算封装数据报外部校验和的技术。

正确校验和的 TCP 或 UDP 数据包的 ones-complement 和等于伪报头之和的补码,因为其他所有内容都被校验和字段“抵消”。这是因为总和在写入校验和字段之前被求补。

更一般而言,这适用于使用“IP 样式” ones-complement 校验和的任何情况,因此适用于 TX 校验和卸载支持的任何校验和。

也就是说,如果我们使用 start/offset 对设置了 TX 校验和卸载,我们知道在设备填写完该校验和后,从 csum_start 到数据包末尾的 ones-complement 和将等于我们事先放入校验和字段的值的补码。这使我们无需查看有效负载即可计算外部校验和:我们只需在到达 csum_start 时停止求和,然后加上 (csum_start + csum_offset) 处 16 位字的补码。

然后,当真实的内部校验和被填写(由硬件或 skb_checksum_help())时,外部校验和将通过算术运算变得正确。

当在 udp_set_csum() 中为 VXLAN 或 GENEVE 之类的封装构造外部 UDP 报头时,堆栈会执行 LCO。对于 udp6_set_csum() 中的 IPv6 等效项也是如此。

它还在 net/ipv4/ip_gre.c:build_header() 中构建 IPv4 GRE 报头时执行。在 net/ipv6/ip6_gre.c:ip6gre_xmit2() 中构建 IPv6 GRE 报头时,当前执行它;GRE 校验和是在整个数据包上计算的,但在这里使用 LCO 应该是可行的,因为 IPv6 GRE 仍然使用 IP 样式的校验和。

所有 LCO 实现都使用 include/linux/skbuff.h 中的一个辅助函数 lco_csum()。

LCO 可以安全地用于嵌套封装;在这种情况下,外部封装层将对自己的报头和“中间”报头求和。这确实意味着“中间”报头将被求和多次,但似乎没有办法在不造成更大成本(例如,在 SKB 膨胀中)的情况下避免这种情况。

RCO:远程校验和卸载

RCO 是一种省略封装数据报的内部校验和,允许卸载外部校验和的技术。但是,它确实涉及对封装协议的更改,接收器也必须支持此更改。因此,默认情况下禁用它。

RCO 在以下 Internet 草案中详细说明

在 Linux 中,RCO 在每个封装协议中单独实现,并且大多数隧道类型都有控制其使用的标志。例如,VXLAN 具有标志 VXLAN_F_REMCSUM_TX(每个结构体 vxlan_rdst),指示在传输到给定远程目标时应使用 RCO。