网卡特性混乱及其解决方案

作者

Michał Mirosław <mirq-linux@rere.qmqm.pl>

第一部分:特性集合

曾经的网卡仅能按字面意思收发数据包的日子一去不复返了。如今的设备增加了多项功能和“缺陷”(也可理解为:卸载),减轻了操作系统在生成和检查校验和、拆分数据包以及对数据包进行分类等方面的各种任务负担。在 Linux 内核领域,这些能力及其状态通常被称为“netdev 特性”。

目前有三组特性与驱动程序相关,另有一组由网络核心层内部使用:

  1. `netdev->hw_features` 集合包含用户可以请求为特定设备更改(启用或禁用)状态的特性。此集合应在 `ndo_init` 回调中初始化,之后不应更改。

  2. `netdev->features` 集合包含当前为设备启用的特性。此集合应仅由网络核心层或在 `ndo_set_features` 回调的错误路径中更改。

  3. `netdev->vlan_features` 集合包含其状态由子 VLAN 设备继承的特性(限制 `netdev->features` 集合)。无论标签是在硬件还是软件中剥离或插入,此集合目前用于所有 VLAN 设备。

  4. `netdev->wanted_features` 集合包含用户请求的特性集合。每当此集合或某些设备特定条件发生变化时,此集合会通过 `ndo_fix_features` 回调进行过滤。此集合是网络核心层内部使用的,不应在驱动程序中引用。

第二部分:控制已启用的特性

当需要更改当前特性集合(`netdev->features`)时,会通过调用 `ndo_fix_features` 回调和 `netdev_fix_features()` 来计算和过滤新的集合。如果结果集合与当前集合不同,它会被传递给 `ndo_set_features` 回调,并且(如果回调成功返回)替换 `netdev->features` 中存储的值。之后,每当当前集合可能发生变化时,就会发出 `NETDEV_FEAT_CHANGE` 通知。

以下事件触发重新计算:
  1. 设备注册后,`ndo_init` 返回成功

  2. 用户请求更改特性状态

  3. 调用了 netdev_update_features()

`ndo_*_features` 回调在持有 `rtnl_lock` 的情况下被调用。缺失的回调被视为始终返回成功。

想要触发重新计算的驱动程序必须在持有 `rtnl_lock` 的情况下调用 netdev_update_features()。这不应在 `ndo_*_features` 回调中完成。驱动程序不应修改 `netdev->features`,除非通过 `ndo_fix_features` 回调。

第三部分:实现提示

  • ndo_fix_features

特性之间的所有依赖关系应在此处解决。结果集合可能会因网络核心层施加的限制(如 `netdev_fix_features()` 中编码的)而被进一步缩小。因此,当某个特性的依赖关系不满足时,更安全的做法是禁用该特性,而不是强制启用其依赖。

此回调不应修改硬件或驱动程序状态(应是无状态的)。它可以在连续的 `ndo_set_features` 调用之间被多次调用。

回调不得更改 `NETIF_F_SOFT_FEATURES` 或 `NETIF_F_NEVER_CHANGE` 集合中包含的特性。`NETIF_F_VLAN_CHALLENGED` 是个例外,但必须注意,因为此更改不会影响已经配置的 VLAN。

  • ndo_set_features

硬件应重新配置以匹配传入的特性集合。除非发生 `ndo_fix_features` 无法可靠检测到的错误情况,否则不应更改该集合。在这种情况下,回调应更新 `netdev->features` 以匹配最终的硬件状态。返回的错误不会(也无法)传播到 `dmesg` 以外的任何地方。(注意:成功返回零,大于零表示静默错误。)

第四部分:特性

有关当前特性列表,请参阅 `include/linux/netdev_features.h`。本节描述其中一些特性的语义。

  • 发送校验和计算

有关完整描述,请参阅 `include/linux/skbuff.h` 文件顶部附近的注释。

注意:`NETIF_F_HW_CSUM` 是 `NETIF_F_IP_CSUM` + `NETIF_F_IPV6_CSUM` 的超集。这意味着设备可以在数据包的任何位置填充类似 TCP/UDP 的校验和,无论其中可能存在什么头部。

  • 发送 TCP 分段卸载

`NETIF_F_TSO_ECN` 意味着硬件可以正确地分割带有 CWR 位设置的数据包,无论是 TCPv4(当 `NETIF_F_TSO` 启用时)还是 TCPv6(`NETIF_F_TSO6`)。

  • 发送 UDP 分段卸载

`NETIF_F_GSO_UDP_L4` 接受一个带有超过 `gso_size` 负载的单个 UDP 头部。在分段时,它会沿着 `gso_size` 边界对负载进行分段,并复制网络和 UDP 头部(如果最后一个分段小于 `gso_size` 则进行修正)。

  • 高内存 DMA 发送

在相关平台上,`NETIF_F_HIGHDMA` 表示 `ndo_start_xmit` 可以处理包含高内存中分段的 `skb`。

  • 发送分散/聚集

这些特性表示 `ndo_start_xmit` 可以处理分段的 `skb`:`NETIF_F_SG` 用于页式 `skb`(`skb_shinfo()->frags`),`NETIF_F_FRAGLIST` 用于链式 `skb`(`skb->next/prev` 列表)。

  • 软件特性

`NETIF_F_SOFT_FEATURES` 中包含的特性是网络堆栈的特性。驱动程序不应根据它们改变行为。

  • VLAN 功能受限

对于无法处理 VLAN 头部(或帧)的设备,应设置 `NETIF_F_VLAN_CHALLENGED`。一些驱动程序设置此特性是因为网卡无法处理更大的 MTU。[FIXME: 这些情况可以通过在 VLAN 代码中仅允许降低 MTU 的 VLAN 来解决。不过,这可能没有用。]

  • rx-fcs

这要求网卡将以太网帧校验序列(FCS)附加到 `skb` 数据的末尾。这允许抓包工具和其他工具读取网卡在接收数据包时记录的 CRC。

  • rx-all

这要求网卡接收所有可能的帧,包括错误帧(例如错误的 FCS 等)。这在嗅探包含错误数据包的链路时非常有用。如果也将其置于普通混杂模式,某些网卡可能会接收更多数据包。

  • rx-gro-hw

这要求网卡启用硬件 GRO(通用接收卸载)。硬件 GRO 基本上是 TSO 的确切逆向操作,并且通常比硬件 LRO 更严格。经硬件 GRO 合并的数据包流必须能够通过 GSO 或 TSO 重新分段回完全原始的数据包流。硬件 GRO 依赖于 RXCSUM,因为每个由硬件成功合并的数据包也必须由硬件验证其校验和。

  • hsr-tag-ins-offload

对于自动插入 HSR(高可用性无缝冗余)或 PRP(并行冗余协议)标签的设备,应设置此项。

  • hsr-tag-rm-offload

对于自动移除 HSR(高可用性无缝冗余)或 PRP(并行冗余协议)标签的设备,应设置此项。

  • hsr-fwd-offload

对于在硬件中将 HSR(高可用性无缝冗余)帧从一个端口转发到另一个端口的设备,应设置此项。

  • hsr-dup-offload

对于在硬件中自动复制传出 HSR(高可用性无缝冗余)或 PRP(并行冗余协议)标签帧的设备,应设置此项。

  • netmem-tx

对于支持 `netmem TX` 的设备,应设置此项。请参阅 网络驱动程序的 Netmem 支持