分段卸载¶
简介¶
本文档描述了 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 重新组装回原始帧。唯一的例外是在给定 IP 头设置了 DF 位的情况下 IPv4 ID。如果 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()
基于这些分割。为了发出此信号,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 中。