校验和卸载

简介

本文档介绍了 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 中声明其卸载功能;有关更多信息,请参见Netdev 特性混乱以及如何摆脱困境。 请注意,仅通告 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()),外部校验和将通过算术运算变得正确。

当为 VXLAN 或 GENEVE 等封装构造外部 UDP 报头时,堆栈会在 udp_set_csum() 中执行 LCO。 IPv6 等效项类似,在 udp6_set_csum() 中。

在 net/ipv4/ip_gre.c:build_header() 中构造 IPv4 GRE 报头时也会执行此操作。 目前不会在构造 IPv6 GRE 报头时执行此操作;GRE 校验和在 net/ipv6/ip6_gre.c:ip6gre_xmit2() 中对整个数据包进行计算,但此处应该可以使用 LCO,因为 IPv6 GRE 仍然使用 IP 样式的校验和。

所有 LCO 实现都使用辅助函数 lco_csum(),位于 include/linux/skbuff.h 中。

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

RCO:远程校验和卸载

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

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

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