vlan 硬件卸载会导致 tcpdump 抓不到包:但驱动构造的 sk_buff 中依然存有元数据

3 阅读4分钟

结论先行:

sk_buff 的“元数据”是 Linux 内核用于管理数据包状态的 C 语言结构体字段,它独立于我们在网络上抓取到的真实“数据包载荷(Payload)”。通过 ethtool -k 命令可以明确查验网卡的 VLAN 硬件卸载(Offload)事实状态。

在软件工程的底层视角下,我们可以通过剥离代码实现的第一性原理,来彻底搞懂这个过程。


第一性原理:解析 sk_buff 与元数据

在 Linux 内核网络子系统中,每一个流动的数据包都被封装在一个极其重要的结构体中:struct sk_buff(Socket Buffer)。

一个 sk_buff 实例在逻辑上严格划分为两部分:

  1. 数据区 (Data Buffer): 这是真正在网线上飞奔的二进制字节流的内存拷贝(包含 MAC 头、IP 头、TCP 载荷等)。这也是 tcpdump 默认直接读取的字节流。
  2. 元数据/控制块 (Metadata / Control Block): 这是内核为了管理这个包而附加的“伴生档案”。它由一系列 C 语言字段组成,例如包的接收时间戳、来源网卡设备指针 (skb->dev),以及我们正在讨论的 VLAN 标志位。

在内核源码(include/linux/skbuff.h)中,与 VLAN 卸载密切相关的元数据字段是:

  • __u16 vlan_tci; (VLAN Tag Control Information,存储实际的 VLAN ID,如 4005)
  • __u16 vlan_present:1; (一个布尔标记,表明这个包是否带有 VLAN 标签)

事实与逻辑:VLAN 硬件卸载的完整生命周期

当网卡开启了 rx-vlan-hwaccel 后,入向报文的处理逻辑如下:

  1. 硬件接收与剥离(事实): 物理网卡(enp94s0f0)的 ASIC 芯片在链路上收到了一个真实的 802.1Q 帧(Ethertype 为 0x8100,VLAN ID 为 4005)。硬件芯片直接“剪掉”了这 4 个字节的 VLAN Tag,将 Ethertype 恢复为 IPv4 的 0x0800
  2. DMA 传输与带外数据(逻辑): 硬件通过 DMA 将被“剪裁”过的数据包拷贝到系统内存(数据区)。同时,硬件将剪下来的 VLAN ID (4005) 放在接收描述符环 (RX Descriptor Ring) 中,作为“带外数据”传递给网卡驱动。
  3. 驱动构造 sk_buff(事实): 网卡驱动程序分配一个 sk_buff。将内存中的数据区挂载上去,同时读取描述符环里的 VLAN ID,将其赋值给元数据:skb->vlan_tci = 4005,并设置 skb->vlan_present = 1

解答疑惑:为什么 tcpdump 没看到?

  • tcpdump 通过 AF_PACKET 套接字抓包,它主要关心的是 数据区 (Data Buffer) 里的内容。
  • 由于硬件已经在第 1 步把物理帧里的 0x8100 标签切掉了,所以 tcpdump 看到的以太网头部自然是 0x0800(IPv4)。
  • 注:现代版本的 libpcap 库其实能够读取 sk_buff 的元数据辅助信息(auxdata),如果在 tcpdump 中加上 -e 参数,有时能看到内核重建打印的 VLAN 信息,但这不改变底层数据区已被硬件修改的事实。

协议栈如何分发?(逻辑闭环)

当报文沿着协议栈向上送到内核的 __netif_receive_skb_core 函数时,内核并不是去查数据区里的 Ethertype,而是优先检查元数据

内核发现 skb->vlan_present == 1vlan_tci == 4005,逻辑触发:内核修改这个 sk_buff 的所属设备指针,将其从 bond1 重定向到订阅了该 VLAN ID 的虚拟子接口 bond1.4005。这就是你在抓包中看到它进入 bond1.4005 In 的根本原因。


数据验证:如何查看和控制 VLAN 硬件卸载

您可以使用 ethtool 工具来查询和更改网卡特性。

1. 查看网卡当前的卸载状态(事实核查):

Bash

ethtool -k enp94s0f0 | grep vlan

预期输出:

您应该会看到类似如下状态,其中 rx-vlan-hwaccelon 证明了上述硬件卸载逻辑正在运行。

Plaintext

rx-vlan-hwaccel: on
tx-vlan-hwaccel: on
...

2. 用于排错的控制命令(验证推测):

在极少数排错场景下,如果您想让硬件“闭嘴”,不要擅自剥离标签,让内核和 tcpdump 看到最原始的链路报文,可以关闭该特性。

(注意:作为 OpenStack/K8s 节点,通常建议保持开启以降低 CPU 负载,排查完毕后请恢复)

Bash

# 关闭接收侧 VLAN 硬件卸载
ethtool -K enp94s0f0 rxvlan off

如果您在关闭后再次运行 tcpdump -i enp94s0f0 -ne,您将确凿地看到物理网卡捕获到的 Ethertype 变成了 0x8100 (802.1Q),且包含 VLAN ID 4005。