分段卸载¶
简介¶
本文档描述了 Linux 网络堆栈中的一组技术,用于利用各种网卡的分段卸载功能。
- 描述了以下技术
TCP 分段卸载 - TSO
UDP 分片卸载 - UFO
IPIP、SIT、GRE 和 UDP 隧道卸载
通用分段卸载 - GSO
通用接收卸载 - GRO
部分通用分段卸载 - GSO_PARTIAL
使用 GSO 加速 SCTP - GSO_BY_FRAGS
TCP 分段卸载¶
TCP 分段允许设备将单个帧分段成多个帧,数据有效负载大小在 skb_shinfo()->gso_size 中指定。当请求 TCP 分段时,应在 skb_shinfo()->gso_type 中设置 SKB_GSO_TCPV4 或 SKB_GSO_TCPV6 的位,并且 skb_shinfo()->gso_size 应设置为非零值。
TCP 分段依赖于对部分校验和卸载的支持。 因此,如果给定设备的 Tx 校验和卸载被禁用,则通常会禁用 TSO。
为了支持 TCP 分段卸载,有必要填充 skbuff 的网络和传输标头偏移量,以便设备驱动程序能够确定 IP 或 IPv6 标头以及 TCP 标头的偏移量。 此外,由于需要 CHECKSUM_PARTIAL,csum_start 还应指向数据包的 TCP 标头。
对于 IPv4 分段,我们支持 IP ID 方面的两种类型之一。 默认行为是为每个段递增 IP ID。 如果指定了 GSO 类型 SKB_GSO_TCP_FIXEDID,那么我们将不递增 IP ID,并且所有段将使用相同的 IP ID。 如果设备设置了 NETIF_F_TSO_MANGLEID,则在执行 TSO 时可以忽略 IP ID,我们将递增所有帧的 IP ID,或者根据驱动程序首选项将其保留为静态值。
UDP 分片卸载¶
UDP 分片卸载允许设备将超大的 UDP 数据报分片成多个 IPv4 片段。 UDP 分片卸载的许多要求与 TSO 相同。 但是,片段的 IPv4 ID 不应递增,因为单个 IPv4 数据报被分片。
UFO 已弃用:现代内核将不再生成 UFO skb,但仍然可以从 tuntap 和类似设备接收它们。 仍然支持基于 UDP 的隧道协议的卸载。
IPIP、SIT、GRE、UDP 隧道和远程校验和卸载¶
除了上述卸载之外,帧还可以包含其他标头,例如外部隧道。 为了解决此类实例,引入了一组额外的分段卸载类型,包括 SKB_GSO_IPXIP4、SKB_GSO_IPXIP6、SKB_GSO_GRE 和 SKB_GSO_UDP_TUNNEL。 这些额外的分段类型用于识别存在多个标头集的情况。 例如,在 IPIP 和 SIT 的情况下,我们应该将网络和传输标头从标准标头列表移动到“内部”标头偏移量。
目前仅支持两个级别的标头。 惯例是将隧道标头称为外部标头,而封装的数据通常称为内部标头。 以下是访问给定标头的调用列表
IPIP/SIT 隧道
Outer Inner
MAC skb_mac_header
Network skb_network_header skb_inner_network_header
Transport skb_transport_header
UDP/GRE 隧道
Outer Inner
MAC skb_mac_header skb_inner_mac_header
Network skb_network_header skb_inner_network_header
Transport skb_transport_header skb_inner_transport_header
除了上述隧道类型之外,还有 SKB_GSO_GRE_CSUM 和 SKB_GSO_UDP_TUNNEL_CSUM。 这两种额外的隧道类型反映了外部标头还请求在外部标头中包含非零校验和的事实。
最后,还有 SKB_GSO_TUNNEL_REMCSUM,它指示给定的隧道标头已请求远程校验和卸载。 在这种情况下,内部标头将保留部分校验和,并且仅计算外部标头校验和。
通用分段卸载¶
通用分段卸载是一种纯软件卸载,旨在处理设备驱动程序无法执行上述卸载的情况。 GSO 中发生的情况是,给定的 skbuff 将其数据分解为多个 skbuff,这些 skbuff 已调整大小以匹配通过 skb_shinfo()->gso_size 提供的 MSS。
在启用任何硬件分段卸载之前,需要在 GSO 中进行相应的软件卸载。 否则,可能会在设备之间重新路由帧,最终无法传输。
通用接收卸载¶
通用接收卸载是 GSO 的补充。 理想情况下,由 GRO 组装的任何帧都应进行分段,以使用 GSO 创建相同的帧序列,并且 GSO 分段的任何帧序列都应能够通过 GRO 重新组装回原始帧。 唯一的例外是 IPv4 ID,在这种情况下,为给定的 IP 标头设置了 DF 位。 如果 IPv4 ID 的值未按顺序递增,则当通过 GRO 组装的帧通过 GSO 分段时,它将被更改为按顺序递增。
部分通用分段卸载¶
部分通用分段卸载是 TSO 和 GSO 的混合体。 它的有效作用是利用 TCP 和隧道的某些特性,因此不必为每个段重写数据包标头,而只需更新最内层的传输标头,并且可能需要更新最外层的网络标头。 这允许不支持隧道卸载或带校验和的隧道卸载的设备仍然可以使用分段。
对于部分卸载,发生的情况是,除了内部传输标头之外的所有标头都会更新,以便它们包含如果标头只是重复的正确值。 唯一的例外是外部 IPv4 ID 字段。 设备驱动程序有责任保证在给定标头未设置 DF 位的情况下,IPv4 ID 字段会递增。
使用 GSO 加速 SCTP¶
SCTP - 尽管缺乏硬件支持 - 仍然可以利用 GSO 通过网络堆栈传递一个大数据包,而不是多个小数据包。
这需要一种不同于其他卸载的方法,因为 SCTP 数据包不能仅仅分段到 (P)MTU。 相反,块必须包含在 IP 段中,并遵守填充。 因此,与常规 GSO 不同,SCTP 不能只生成一个大的 skb,将 gso_size 设置为分片点并将其传递到 IP 层。
相反,SCTP 协议层构建一个 skb,其中段已正确填充并存储为链接的 skb,并且 skb_segment()
基于这些 skb 进行拆分。 为了发出此信号,gso_size 设置为特殊值 GSO_BY_FRAGS。
因此,核心网络堆栈中的任何代码都必须意识到 gso_size 可能是 GSO_BY_FRAGS,并适当地处理这种情况。
有一些助手可以简化此操作
skb_is_gso(skb) && skb_is_gso_sctp(skb) 是查看 skb 是否为 SCTP GSO skb 的最佳方式。
对于大小检查,skb_gso_validate_*_len 系列助手会正确考虑 GSO_BY_FRAGS。
对于操作数据包,skb_increase_gso_size 和 skb_decrease_gso_size 将检查 GSO_BY_FRAGS,如果要求操作这些 skb,则发出 WARN。
这也影响设置了 NETIF_F_FRAGLIST & NETIF_F_GSO_SCTP 位的驱动程序。 另请注意,NETIF_F_GSO_SCTP 包含在 NETIF_F_GSO_SOFTWARE 中。