网卡特性混乱及其解决方案¶
- 作者
Michał Mirosław <mirq-linux@rere.qmqm.pl>
第一部分:特性集合¶
曾经的网卡仅能按字面意思收发数据包的日子一去不复返了。如今的设备增加了多项功能和“缺陷”(也可理解为:卸载),减轻了操作系统在生成和检查校验和、拆分数据包以及对数据包进行分类等方面的各种任务负担。在 Linux 内核领域,这些能力及其状态通常被称为“netdev 特性”。
目前有三组特性与驱动程序相关,另有一组由网络核心层内部使用:
`netdev->hw_features` 集合包含用户可以请求为特定设备更改(启用或禁用)状态的特性。此集合应在 `ndo_init` 回调中初始化,之后不应更改。
`netdev->features` 集合包含当前为设备启用的特性。此集合应仅由网络核心层或在 `ndo_set_features` 回调的错误路径中更改。
`netdev->vlan_features` 集合包含其状态由子 VLAN 设备继承的特性(限制 `netdev->features` 集合)。无论标签是在硬件还是软件中剥离或插入,此集合目前用于所有 VLAN 设备。
`netdev->wanted_features` 集合包含用户请求的特性集合。每当此集合或某些设备特定条件发生变化时,此集合会通过 `ndo_fix_features` 回调进行过滤。此集合是网络核心层内部使用的,不应在驱动程序中引用。
第二部分:控制已启用的特性¶
当需要更改当前特性集合(`netdev->features`)时,会通过调用 `ndo_fix_features` 回调和 `netdev_fix_features()` 来计算和过滤新的集合。如果结果集合与当前集合不同,它会被传递给 `ndo_set_features` 回调,并且(如果回调成功返回)替换 `netdev->features` 中存储的值。之后,每当当前集合可能发生变化时,就会发出 `NETDEV_FEAT_CHANGE` 通知。
- 以下事件触发重新计算:
设备注册后,`ndo_init` 返回成功
用户请求更改特性状态
`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 支持