InfiniBand 专题【左扬精讲】—— InfiniBand 流量抓取与分析:ibdump 与 Wireshark 实战

InfiniBand 专题【左扬精讲】—— InfiniBand 流量抓取与分析:ibdump 与 Wireshark 实战

本系列前面十五篇讲的都是"配置"与"打流",但没有一篇讲过"怎么证明它真的按你配的方式在跑"。配置是对未来的承诺,抓包是对当下的取证。网卡上那条报文到底查到了哪条转发表、某个 QP 到底发了几个 WRITE、为什么 WRITE 后面没有应答——这些只有抓包能回答。

本篇要解决的就是这件事。开始之前,先说清楚课程原文里几处容易听错的地方,因为它们直接决定后面的操作会不会白做。

其一,课程说"能看到被抓到的 IB 数据包带着各自的头部:LRH、BTH 和 ETH",这句需要修正。准确说法是每一帧都有 LRH(Local Route Header,本地路由头)与 BTH(Base Transport Header,基础传输头),而 ETH 不是固定的第三个头——转写里的 "ETH" 是 AETH(Acknowledgment Extended Transport Header,应答扩展传输头)的残缺,它只在需要应答的操作码上出现,并非必现。按"三个头必备"去理解抓包结果,会在 WRITE 请求里找不到 AETH 而误判为抓包不完整。

其二,课程说 tcpdump 和 windump "只捕获 TCP/IP 数据包",这句话是对的,但原因课程没讲。它们不是简单地"只认 TCP/IP",而是取数路径就挂在协议栈上。IB 报文从网卡到应用不经过那条栈,所以挂不上去。这一层区别是本篇的技术核心,第三节展开。

其三,课程说 ibdump 抓"流经与流出 InfiniBand 端口的流量",这个"端口"要落到具体参数上。OFED 的 ibdump 默认抓 1 号端口,双端口 HCA 上抓第二个口必须显式给 -i 2,否则抓到的是空文件而不是报错。这是实践中最常见的"抓不到"来源。

InfiniBand流量抓取ibdumpWiresharkpcapERFLRHBTHMLNX_OFED协议分析

必须知道

  • 抓包分两个动作:捕获与 显示,技术难度完全不同,工业界事实上分成"采集器"与"分析器"两类工具
  • 三者都围绕 pcap 工作:tcpdump / windump 产出,ibdump 也产出,Wireshark 消费——文件即契约,因此采集与分析可以跨机器、跨操作系统
  • IB 报文绕过协议栈:LRH 里直接编码网络层、BTH 里直接编码 DestQP——IB 不是"另一种 IP",而是与 IP 并列的另一套端到端协议
  • ibdump 产出的是 DLT_ERF = 197 而非规范登记的 LINKTYPE_INFINIBAND = 247,详见 8.6
  • 丢包不报告:Captured: N packets 里的 N 是"成功采集到的包数",不是"链路上传输的包数"

必须掌握

  • LRH 固定 8 字节六字段,其中 Packet Length 单位是 4 字节字且包含 LRH 自身,算净载荷要减掉一整串头
  • BTH 固定 12 字节,Opcode 的高 3 位直接编码传输服务(RC 0 / UC 1 / RD 2 / UD 3)
  • lrh.lnh == 2 是子网内、== 3 是跨子网——这是不查任何配置就能做的判据
  • 抓 0 字节的五步排除顺序:端口状态 → 端口号 → 抓取方向 → 设备名 → 链路层类型

一、抓包是什么:两个动作与四类使用者

1.1 抓包的定义里藏着两个动作

课程给的定义是:网络报文分析器或嗅探器捕获数据包,然后尽可能详细地显示包数据。这个定义里有两个动作——捕获与 显示。

把这两个动作拆开看,就能理解本篇反复出现的一个现象:同一件事,用不同的工具做,能力完全不同。显示能力是纯软件工程决定的,只要字节完整,Wireshark 就能把 IB 报文逐字段拆开给你看;而捕获能力由数据从网卡到内存这条物理路径决定,这条路径能不能被截断,取决于操作系统协议栈的结构。

这一层区分不是概念游戏,它直接决定排障方向。遇到"抓不到"时,第一个要问的问题是"是捕获失败还是显示失败"。如果 Wireshark 打开文件后一片空白,那是文件本身没内容;如果文件有内容但字段全乱,那才是显示端的问题。把这两类问题混在一起,就会出现"换分析器"这种无效动作——文件是空的,换十个分析器还是空的。

1.2 四类使用者与四种判读层次

课程列出四类使用者。同一个抓包文件,四个角色关心的字段并不重叠:

角色用抓包做什么主要看哪些字段
网络管理员 排查网络问题 lrh.dlid / lrh.slid(确认可达性与转发路径)、重传与 bth.psn 连续性
网络安全工程师 检查安全问题 bth.p_key(分区隔离是否有效)、会话标识与非法 bth.destqp 访问
开发者 调试协议实现 bth.opcode / bth.psn / aeth.syndrome(我发出的报文对不对、错在哪一位)
教师与学生 教学或学习网络协议 逐字段位域划分(0xF0 / 0x0F / 0x07FF 这些掩码到底切在哪)

这张表说明了一件事:抓包不是"调试专用工具",它是把线上真实字节变成可读证据的唯一手段。网管看转发路径、安全看分区、开发看错误码——四个角色用同一份文件,取的是不同字段。

1.3 采集器与分析器的分界

按 1.1 的两个动作切分,本篇涉及的工具可以这样归类:

工具谱系:谁负责捕获,谁负责显示
工具捕获显示产出/消费格式能抓 IB 吗
tcpdump 是(挂协议栈) 简要 产出 pcap 不能
windump 是(挂协议栈) 简要 产出 pcap 不能
ibdump 是(HCA 队列嗅探) 无 产出 pcap 能
Wireshark 需另配通道 逐字段 消费 pcap 能(前提是文件对)

这张表里最关键的一格是最后一行:Wireshark 的 IB 分析能力建立在"文件已经正确"这个前提上。课程说 Wireshark "是 web UI 基础的,并且有许多集成的排序与过滤选项"——这些说的全是显示与交互能力,没有一条在说捕获能力。这个偏置是有道理的,因为显示能力才是 Wireshark 的核心竞争力,但它带来一个后果:

Wireshark 的分析能力是协议无关的,它缺的不是"会读 IB",而是"喂进来的字节不对"。把 tcpdump 换成 Wireshark 去抓 IB 流量,并不会让字节变对。课程说"如果你熟悉 Wireshark 分析 TCP/IP 流量,会发现用它分析 InfiniBand 流量同样非常方便"——这句"方便"成立的前提是抓包文件里的字节完整且链路类型正确,两件事都由采集端决定。

还有一条实践上必须记住的代价:抓包本身会占用资源。采集器要在 HCA 上占用队列、内存与 CPU 时间,这些开销会与业务流量竞争。抓包不是零成本操作,不要在生产环境长时间挂着。第八节会回到这个话题。

二、通用抓包工具:tcpdump、windump 与 pcap

2.1 tcpdump 与 windump

课程的定义是:tcpdump 是一个常见的报文分析器,它允许显示 TCP / IP 数据包以及通过本机所连网络传输的其他数据包;windump 是 tcpdump 的 Windows 版本。

这两个工具的定位要分清:它们在"报文分析器"这个类目下,但在这门课里被当作采集器使用。它们的主要输出是 pcap 文件,而不是终端上的滚动文本。

  • ✔ 课程对二者关系的表述是准确的:windump 就是 tcpdump 的 Windows 版,选项语义一致
  • ✔ 可互换这一点有实际价值:Windows 上抓的文件拿到 Linux 上用 Wireshark 打开没有任何问题
  • ✗ 但它们对本课程的目标流量无效:原因见第三节,与平台无关——在 Linux 上用 tcpdump 同样抓不到 IB 报文

2.2 pcap 格式:本课程真正的接口

课程说"这些工具生成一个已知为 pcap 格式的文件,用 pcap 扩展名标识"。这句话里真正重要的不是扩展名,而是格式本身构成了一份契约。

这份契约带来三个直接后果:

  • 采集与分析可以分离:抓包发生在出问题的机器上,分析发生在有图形界面的工作站上
  • 可以跨操作系统:OFED README 明确写出一条实用信息——ibdump 是 Linux 应用,但生成的 pcap 文件可以在任意操作系统上分析
  • 格式选择成为实现细节,却会决定兼容性:本篇 8.6 会讲一个真实的坑——ibdump 写入的链路类型编号不是规范为 IB 登记的那个

课程最后提到一句对新手很实用的指引:如果你是 Wireshark 的新手,Wireshark 的官方网站是一个好的起点。这句放在这里其实有更深的含义——Wireshark 的分析能力是协议无关的,它缺的不是"会读 IB",而是"喂进来的字节不对",所以官方文档能解决显示端的任何问题,但解决不了采集端的问题。工具选型的判断力比工具本身的操作熟练度更重要。

三、为什么 tcpdump 抓不到 InfiniBand 流量

3.1 课程给的因果链

课程的说法是:tcpdump 和 windump 只捕获 TCP / IP 数据包,而 InfiniBand 数据包绕过了协议栈。这条因果链是本篇的核心,值得逐层展开。

3.2 展开:两条取数路径的根本差异

"绕过协议栈"这句话可以用一张图说清楚。左侧是以太网路径,右侧是 InfiniBand 路径,两者的关键区别在于报文从网卡到应用之间经过几个环节:

  
网线 / 光纤                      HCA 端口
    |                                |
    v                                v
+----------+                  +-------------------+
| 网卡 NIC |                  | 网卡 NIC + HCA    |
+----------+                  | (含硬件嗅探逻辑)  |
    |                                |
    v                                |
+----------+                         |  不经过 IP/TCP 协议栈
| IP 协议栈|                         |
| TCP/UDP  |                         |
+----------+                         |
    |                                |
    v                                |
+----------+                         v
| socket  |                  +-------------------+
+----------+                  | 应用 / verbs     |
    ^                                |
    |                                |
+---------------------+              |
| tcpdump 挂在这里     |              |
| (BPF / raw socket)   |              |
+---------------------+              |
                                 +-------------------+
                                 | ibdump 挂在这里   |
                                 | (建嗅探 QP 直取)  |
                                 +-------------------+
  

这张图里最该记住的是最下面那两条线。抓包工具能挂上去,取决于网卡到应用之间有没有它能插队的那个点。以太网路径上有 IP 协议栈、有 socket 层,通用嗅探器就挂在 socket 层旁边;IB 路径上没有那条栈,应用直接用 verbs 走硬件队列,中间没有可挂载点,所以不是 tcpdump "不愿意"抓,而是没有它能待的地方。

3.3 IB 报文自带网络层与传输层标识

协议栈旁路这件事,在报文格式上留下的痕迹比"绕过"二字更具体:

  • 网络层:IP 报文靠 IPv4 / IPv6 头携带目的地址,而 IB 报文以 LRH 开头,网络层信息直接编码在 LRH 里——目的 LID、虚拟通道、服务等级、源 LID 全部在 8 字节内,不需要任何 IP 头
  • 传输层:TCP / UDP 靠端口号复用连接,而 IB 报文以 BTH 开头,DestQP 直接用 24 位编码目的队列对,不需要端口号

所以从报文结构看:IB 不是"另一种 IP",而是与 IP 并列的另一套端到端协议。它有自己的网络层头部(LRH / GRH)和传输层头部(BTH),不借道 IP 体系。这也解释了为什么 RoCE 是个特例——RoCE 把 IB 传输层封装在以太网帧里,所以 RoCE 流量能被 tcpdump 抓到,但抓到的外层是以太网头,IB 头在里面。

本节结论:遇到"抓不到 IB 流量"时,正确方向是换工具,不是改过滤表达式。过滤表达式是抓到手之后才生效的,对"抓不到"这个问题无能为力。

四、ibdump:OFED 里的 InfiniBand 嗅探器

4.1 工具定位

OFED README 对 ibdump 的定位说得很清楚:它转储流经 Mellanox 适配器卡、流向与流出适配器卡的 InfiniBand 流量,并提供与以太网上 tcpdump 工具类似的功能。README 进一步说明:它生成 pcap 格式的转储文件,可以用 Wireshark 加载做图形化流量分析;使用户能分析网络行为与性能,并调试发送或接收 InfiniBand 流量的应用。

课程原文对照:课程把工具定位表述为"捕获流经 InfiniBand 端口、并从该端口流出的 InfiniBand 流量",与 README 第一段一致。课程还提到"先运行 ibdump 命令并带上期望的选项",对应 README 末尾的 "run \"ibdump -h\"" 查看选项说明。

把"类似的功能"这四个字理解到位,工具选型就清楚了:在 IB 世界里,ibdump 填的正是 tcpdump 在以太网世界里的位置。它的名字、参数风格(-d / -i / -w)、产出格式、管道用法处处对齐 tcpdump,唯一不同的是它挂载的位置。

一条与实践相关的信息:OFED README 写明 "The ibdump repository will be removed by the end of 2026. You can use ibdump from the mstflint project."——该独立仓库将在 2026 年底前移除,功能并入 mstflint 项目。这不影响当前 OFED 发行版里的 ibdump,但对"以后去哪里找这个工具"这个问题给出了明确答案。

4.2 命令行:课程讲的两个参数与源码里的完整选项表

课程明确讲了三个要点:用 -d 标志加上 HCA 名字,来选择要为其抓包的相关设备或 HCA;可以加上 -w 标志加上文件名,把数据保存到特定文件,默认文件名是 sniffer.pcap,在当前工作目录创建。

但源码 ibdump.c 的 usage() 里选项比课程讲的多,下面是完整原文(%s 与 %d 处为运行时填充的默认值):

  
 ibdump - Dump Infiniband traffic from Mellanox Technologies ConnectX HCA
 The dump file can be loaded by Wireshark for graphical traffic analysis

Usage:
 ibdump [options]

Options:
 -d, --ib-dev= use IB device (default first device found)
 -i, --ib-port= use port of IB device (default 1)
 -w, --write= dump file name (default "sniffer.pcap")
     '-' stands for stdout - enables piping to tcpdump or tshark.
 -o, --output= alias for the '-w' option. Do not use - for backward compatibility
 -b, --max-burst= log2 of the maximal burst size that can be
     captured with no packets loss.
     Each entry takes ~ MTU bytes of memory (default 12 - 4096 entries)
 -s, --silent do not print progress indication.
 -T, --conti Use contiguous pages.
 -M, --mem-mode when specified, packets are written to file only after the capture
     is stopped. It is faster than default mode (less chance for
     packet loss), but takes more memory. In this mode, ibdump
     stops after bytes are captured
 -p, --writer-thread Use a specific thread for writing data to disk.
     In order to use this functionality you have to specify the
     size of two temporary buffers used to save data while the
     thread writes to disk
 --decap Decapsulate port mirroring headers. Should be used
     when capturing RSPAN traffic.
 -h, --help Display this help screen.
 -v, --version Print version information.
  

这张表里有三处课程没提但直接影响实践的细节:

  • -w - 表示输出到标准输出——帮助原文 "'-' stands for stdout - enables piping to tcpdump or tshark.",这是 8.5 管道模式的依据
  • -b 收的是 log2 而不是条目数——帮助原文 "log2 of the maximal burst size",并且明确 "Each entry takes ~ MTU bytes of memory"。传 -b 4096 得到的不是 4096 条目而是 2^4096
  • -o 是 -w 的别名但不要给它传 -——帮助原文 "alias for the '-w' option. Do not use - for backward compatibility"

此外 getopt_long 的选项串是 "p:w:d:i:o:b:hM:vC:EI:Lq:sm:jT",长选项表里还有几个课程与帮助文本都没展开的选项:-I / --direction=(tx 或 rx,源码里传其它值会报 "Bad parameter for direction flag")、-q / --src_qp=(只看指定源 QP 的报文)、-E / --decap(剥离端口镜像头,抓 RSPAN 流量用)、--erf(显式指定是否写 ERF 头)、-j / --jumbo-mtu、-s / --silent(不打印进度)。

  
# 最小可用形式:指定设备,使用默认文件名 sniffer.pcap
[root@node1 ~]# ibdump -d mlx5_0

# 显式指定输出文件(推荐)
[root@node1 ~]# ibdump -d mlx5_0 -w ib_business.pcap

# 双端口 HCA 上抓 2 号端口
[root@node1 ~]# ibdump -d mlx5_0 -i 2 -w ib_business.pcap

# 只看某个源 QP 的报文,并把结果管道给 tshark
[root@node1 ~]# ibdump -d mlx5_0 -w - | tshark -i - -Y 'infiniband.bth.opcode == 0x0A'
  

一条需要澄清的点:ibdump 没有"抓 N 个包就停"的选项。它的 getopt_long 选项串是 "p:w:d:i:o:b:hM:vC:EI:Lq:sm:jT",里面没有 -c。课程里"让流量运行一段时间再停止"因此是必要步骤,不是可以省略的客套话。

能用来自动停止的只有 -M <bytes>——帮助原文 "In this mode, ibdump stops after bytes are captured"。其余情况下停止只能靠 Ctrl+C(6.4 已说明它是优雅退出,文件完整)。

4.3 启动输出:五个字段决定后续判读方向

启动后 ibdump 会调用 print_config() 打印一段配置。源码里的真实实现非常精简,只有六行:

  void print_config(void)
{
    fprintf(stdout, " ------------------------------------------------\n");
    /* TODO: Add RX/TX/BOTH mode */
    fprintf(stdout, " Device         : \"%s\"\n", config.dev_name);
    fprintf(stdout, " Physical port  : %u\n", config.ib_port);
    fprintf(stdout, " Link layer     : %s\n", config.is_eth ? "Ethernet" : "Infiniband");
    fprintf(stdout, " Dump file      : %s\n", config.out_file_name);
    fprintf(stdout, " Sniffer WQEs (max burst size) : %u\n", config.entries_num);

    if (config.src_qp_str) {
        fprintf(stdout, " Monitored QPs: : %s\n", config.src_qp_str);
    }
    fprintf(stdout, " ------------------------------------------------\n\n");
}

对应到实际输出(字段名与顺序完全对应源码):

  
 ------------------------------------------------
 Device         : "mlx5_0"
 Physical port  : 1
 Link layer     : Infiniband
 Dump file      : ib_business.pcap
 Sniffer WQEs (max burst size) : 4096
 ------------------------------------------------

  

这段输出里有两个字段必须在抓包前先看。其一,Link layer——它由 config.is_eth 决定,显示 Infiniband 才是 IB 模式。如果显示 Ethernet,说明这张卡跑在 RoCE 模式,此时抓到的外层是以太网头,不是 IB 报文,第五节整套字段判读全部不成立。其二,Physical port——确认抓的是哪个口,默认 1。

另外注意源码里 print_config() 开头留着一条 /* TODO: Add RX/TX/BOTH mode */ 注释——方向信息目前不在配置输出里显示,只能靠命令行 -I 自己记住。

4.4 默认值:来自 ibdump.h 的静态配置表

源码 ibdump.h 的 struct config_t config 静态初始化表列出了全部默认值,这是本篇所有默认值结论的唯一出处:

  struct config_t config = {
    NULL,                     /* dev_name */
    NULL,                     /* mst_dev_name */
    "sniffer.pcap",           /* out file name */
    1,                        /* ib_port */
    0,                        /* mem size */
    0,                        /* decap_mode */
    12,                       /* log2entries_num */
    4096,                     /* entries_num */
    ERF_TYPE_INFINIBAND,      /* erf_type: InfiniBand (21) */
    NULL,                     /* src_qp_str */
    0,                        /* is_silent */
    0,                        /* is_eth */
    0,                        /* to_stdout */
    -1,                       /* with_erf: -1 = default per proto */
    0,                        /* jumbo_mtu */
    0,                        /* use_a0_mode */
    0,                        /* contiguous_pages */
    0,                        /* writer_thread */
    0                         /* mem_mode */
};

把这张表里对本篇结论有直接影响的三项单列出来:

字段默认值实践含义
out_file_name "sniffer.pcap" 不指定 -w 时在工作目录生成 sniffer.pcap,与课程表述一致
ib_port 1 双端口 HCA 抓 2 号口必须 -i 2
log2entries_num / entries_num 12 / 4096 捕获窗口初始大小,丢包分析的关键参数;源码在参数解析后执行 config.entries_num = 1 << config.log2entries_num; 重新计算
with_erf -1 表示"按协议自动决定",IB 模式自动为 1,ETH 模式自动为 0,见 8.6
erf_type ERF_TYPE_INFINIBAND 即 21,见 8.6

这三个默认值都不是随便定的。输出文件名沿用 tcpdump 风格;端口默认 1 是因为单端口场景最常见;条目数 4096 的理由帮助文本写得很明确——"Each entry takes ~ MTU bytes of memory",也就是 4096 条目约等于 4096 × MTU 字节的内存占用,是主机内存与抗突发能力之间的折中。

4.5 两条硬约束:固件版本门槛与选项互斥

ibdump 有两条会直接导致失败的要求,课程没有提到。

约束一,固件版本门槛。源码里有三个版本常量,每条都带解释性注释:

  #define MIN_FW_REQUIRED_ETH    "2.11.1140" /* This is the initial version with sniffer rules support. RX only. */
#define MIN_FW_REQUIRED_IB_RC  "2.33.1330" /* This version contains a fix for sniffing in ib mode. */
#define MIN_FW_REQUIRED_IB     "2.33.5000" /* This is the rc version that contains the fix in ib mode. */

版本不足时的报错是 Device firmware version ... does not support sniffing。三条注释给出了完整信息:ETH 模式从 2.11.1140 起支持嗅探规则,但仅 RX 方向;IB 模式的两个门槛分别是 2.33.1330(通用)与 2.33.5000(RC 方向)。这条约束与本系列第十四篇讲的 MFT 工具链直接相关——固件版本本身要用 flint / mlxfwmanager 管理。

约束二,-M 与 -p 互斥。源码在参数解析后显式检查并报错:-E- You can not use mem-mode and writer-thread simultaneously.,随后 return 1。原因是这两个选项分别对应"全部缓存内存、停止时才落盘"与"独立写盘线程",两者都要独占写盘路径。

还有一条编译期约束来自 README:ibdump 不支持第五代 IB 设备(ConnectX-4 及更新)在未带 FW tools 编译时的抓包——README 原文是 "Ibdump does not support 5th gen IB devices (ConnectX-4 & newer) when compiled without FW tools (when using make WITHOUT_FW_TOOLS=yes)",解决办法是改用其他编译配置(MFT Library、MSTFLINT Library 等)。

五、Wireshark 中的 InfiniBand 字段体系

5.1 LRH:固定 8 字节的本地路由头

用 Wireshark 打开 ibdump 抓的 pcap 文件,每一帧会按 LRH / BTH / 后续头的顺序拆开。课程提到的 "LRH" 就是 Local Route Header,它固定 8 字节,含六个字段。

Wireshark 的解析器把这些字段暴露成 infiniband.lrh.* 过滤器字段,定义取自源码 packet-infiniband.c 的 proto_register_infiniband()(原文为多行结构体,此处按字段压缩):

  /* Local Route Header (LRH) */
{ &hf_infiniband_virtual_lane,        { "Virtual Lane",        "infiniband.lrh.vl",     FT_UINT8,  BASE_HEX, NULL, 0xF0,   NULL, HFILL} },
{ &hf_infiniband_link_version,        { "Link Version",        "infiniband.lrh.lver",   FT_UINT8,  BASE_DEC, NULL, 0x0F,   NULL, HFILL} },
{ &hf_infiniband_service_level,       { "Service Level",       "infiniband.lrh.sl",     FT_UINT8,  BASE_DEC, NULL, 0xF0,   NULL, HFILL} },
{ &hf_infiniband_reserved2,           { "Reserved (2 bits)",   "infiniband.lrh.reserved2", FT_UINT8, BASE_DEC, NULL, 0x0C,  NULL, HFILL} },
{ &hf_infiniband_link_next_header,    { "Link Next Header",    "infiniband.lrh.lnh",    FT_UINT8,  BASE_HEX, NULL, 0x03,   NULL, HFILL} },
{ &hf_infiniband_destination_local_id,{ "Destination Local ID","infiniband.lrh.dlid",   FT_UINT16, BASE_DEC, NULL, 0x0,    NULL, HFILL} },
{ &hf_infiniband_reserved5,           { "Reserved (5 bits)",   "infiniband.lrh.reserved5", FT_UINT16, BASE_DEC, NULL, 0xF800, NULL, HFILL} },
{ &hf_infiniband_packet_length,       { "Packet Length",       "infiniband.lrh.pktlen", FT_UINT16, BASE_DEC, NULL, 0x07FF, NULL, HFILL} },
{ &hf_infiniband_source_local_id,     { "Source Local ID",     "infiniband.lrh.slid",   FT_UINT16, BASE_DEC, NULL, 0x0,    NULL, HFILL} }

把这张表按字节重新排布,就是 LRH 的完整布局:

字节字段掩码Wireshark 过滤字段
0 高 4 位 Virtual Lane 0xF0 infiniband.lrh.vl
0 低 4 位 Link Version 0x0F infiniband.lrh.lver
1 高 4 位 Service Level 0xF0 infiniband.lrh.sl
1 bits 3:2 Reserved(2 bits) 0x0C infiniband.lrh.reserved2
1 低 2 位 Link Next Header 0x03 infiniband.lrh.lnh
2:3 Destination LID 16 位 infiniband.lrh.dlid
4 高 5 位 Reserved(5 bits) 0xF800 infiniband.lrh.reserved5
4 低 11 位 ~ 5 Packet Length 0x07FF infiniband.lrh.pktlen
6:7 Source LID 16 位 infiniband.lrh.slid

源码在读出 PktLen 之后连做三步,注释都很直白:

  packetLength = packetLength & 0x07FF; /* Mask off top 5 bits, they are reserved */
packetLength = packetLength * 4; /* Multiply by 4 to get true byte length. This is by specification. */
packetLength -= 8; /* Shave 8 bytes for the LRH. */

三条必须记住的细节。其一,Packet Length 的单位是 4 字节字——第二行就是 * 4,注释写明 "Multiply by 4 to get true byte length. This is by specification.",所以显示 1024 实际是 4096 字节。其二,它包含 LRH 自身——第三行 -= 8 的注释是 "Shave 8 bytes for the LRH.";源码随后还会按头序继续减 GRH 40 字节、bthSize、RETH 16 / DETH 8 / RDETH 4 / IMMDT 4 / AETH 4 / IETH 4 / DCCETH 16,所以显示值与"载荷长度"之间差着一整串头。其三,源与目的被写进 Wireshark 的地址列,类型是 AT_IB,所以你在源/目的列看到的是 LID 数值而不是 IP 地址。

5.2 BTH:12 字节与操作码的位域规律

BTH 是 Base Transport Header,固定 12 字节。字段注册同样来自 proto_register_infiniband(),注释块写的是 "Base Transport Header (BTH)":

字段Wireshark 过滤字段类型 / 掩码说明
Opcode infiniband.bth.opcode FT_UINT8,VALS(bth_opcode_tbl) 高 3 位编码传输服务
Solicited Event infiniband.bth.se FT_BOOLEAN,0x80 是否请求完成事件
MigReq infiniband.bth.m FT_BOOLEAN,0x40 迁移请求位
Pad Count infiniband.bth.padcnt 掩码 0x30 负载区填充字节数
Header Version infiniband.bth.tver 掩码 0x0F 传输头版本
P_Key infiniband.bth.p_key 16 位 分区键
DestQP infiniband.bth.destqp FT_UINT24 目的队列对,24 位
Acknowledge Request infiniband.bth.a FT_BOOLEAN,0x80 是否请求应答
Reserved(7 bits) infiniband.bth.reserved7 掩码 0x7F 保留位
PSN infiniband.bth.psn FT_UINT24 包序号,24 位

把 BTH 排成图,位域关系一目了然:

  
字节 0        字节 1        字节 2:3      字节 4:5    字节 6:7     字节 8     字节 9    字节 10:11
+------------+------------+------------+----------+----------+----------+---------+-----------+
| Opcode     |Se|M|Pad|TVer|     P_Key      |         DestQP          |A|Resv  |    PSN     |   AETH     |
|  8 bits    |E  | |Cnt|4b  |    16 bits    |         24 bits         | |7 bits |   24 bits   |  12 bits   |
+------------+------------+------------+----------+----------+----------+---------+-----------+
 \___ 传输服务 ___/ \_版本_/ \___ 分区键 ___/ \______ 目的队列对 ______/ \___ 序号 ___/ \_ 应答 _/
       高 3 位 = 传输服务类型
  

解读的关键是这条位域规律:操作码的高 3 位直接编码传输服务。源码宏定义为:

  /* Infiniband transport services
 These are an enumeration of the transport services over which an IB packet
 might be sent. The values match the corresponding 3 bits of the opCode field
 in the BTH */
#define TRANSPORT_RC 0
#define TRANSPORT_UC 1
#define TRANSPORT_RD 2
#define TRANSPORT_UD 3

注释写得很明确:这些值对应 BTH 里 opCode 字段的高 3 位。所以 8 位操作码被切成若干段,每段 32 个取值。从源码 bth_opcode_tbl 表里取几个具名项就能看清这个结构:

  bth_opcode_tbl[] = {
    { 0x0,  "Reliable Connection (RC) - SEND First" },
    { 0xA,  "Reliable Connection (RC) - RDMA WRITE Only" },
    { 0xB,  "Reliable Connection (RC) - RDMA WRITE Only with Immediate" },
    { 0xC,  "Reliable Connection (RC) - RDMA READ Request" },
    { 0x11, "Reliable Connection (RC) - Acknowledge" },
    { 0x1D, "Reliable Connection (RC) - ATOMIC WRITE" },
    /* ... */
    { 0x2A, "Unreliable Connection (UC) - RDMA WRITE Only" },
    { 0x2B, "Unreliable Connection (UC) - RDMA WRITE Only with Immediate" },
    /* ... */
    { 0x4A, "Reliable Datagram (RD) - RDMA WRITE Only" },
    { 0x4B, "Reliable Datagram (RD) - RDMA WRITE Only with Immediate" },
    { 0x51, "Reliable Datagram (RD) - Acknowledge" },
    /* ... */
    { 0x64, "Unreliable Datagram (UD) - SEND only" },
    { 0x65, "Unreliable Datagram (UD) - SEND only with Immediate" },
    { 0x80, "CNP" },
    { 0xA0, "Extended Reliable Connection (XRC) - SEND First" },
    { 0xAA, "Extended Reliable Connection (XRC) - RDMA WRITE Only" },
    /* ... */
    { 0, NULL}
};

这张表给出四条可直接使用的判读规则。其一,同一个 WRITE 语义动作在四个传输服务段里各有一个位置——0x0A(RC)/ 0x2A(UC)/ 0x4A(RD)/ 0xAA(XRC)。其二,不能脱离传输服务谈数值——纯按数值过滤会把不同传输服务的报文混在一起。其三,看到操作码就已经知道这条报文走哪种可靠性语义——RC 段有重传与应答,UD 段发出去就不管,所以判断"这个操作为什么丢了"要先看它属于哪一段。其四,0x80 = CNP 是这张表里唯一一个与拥塞控制直接相关的操作码,它不在 RC/UC/RD/UD 任何一段里,而是独立占用 0x80。

5.3 LNH:区分子网内与跨子网

LRH 第二个字节的低 2 位是 Link Next Header,它决定 BTH 之后该读什么。源码在读完 LRH 第二个字节后把低 2 位存入 lnh_val,再用一个 switch 决定下一步。四个宏是 RAW = 0 / IP_NON_IBA = 1 / IBA_LOCAL = 2 / IBA_GLOBAL = 3。

对抓包判读来说只有两个取值重要:

  • LNH = 2(IBA_LOCAL)——后面直接是 BTH,是子网内通信
  • LNH = 3(IBA_GLOBAL)——后面先跳过 GRH(ibdump.h 定义 GRH_SIZE 40,源码注释 "Shave 40 bytes for GRH")再读 BTH,此时 Wireshark 的源/目的列会被改写成 128 位 GID 地址——源码里对应 set_address_tvb(&pinfo->src, AT_IB, GID_SIZE, tvb, offset);,而子网内场景对应的是 set_address(&pinfo->src, AT_IB, sizeof(uint16_t), src_addr);

由此得到一个纯抓包可做的判据:看 infiniband.lrh.lnh 的值就能区分这个包是子网内还是跨子网,不需要查任何配置。这条判据在 7.3 会作为一个具体动作展开——当你在抓包里看到源/目的列从 LID 数字突然变成一串 GID 地址时,就知道这条报文走了路由器。

5.4 后续头:由操作码决定的 27 种组合

BTH 之后解析器怎么走,完全由操作码决定。源码为此维护了一张头序枚举表,注释写明 "These are simply an enumeration of the possible header combinations defined by the IB Spec.",共 27 个具名组合(编号 0–26):

  #define RDETH_DETH_PAYLD                0
#define RDETH_DETH_RETH_PAYLD           1
#define RDETH_DETH_IMMDT_PAYLD          2
#define RDETH_DETH_RETH_IMMDT_PAYLD     3
#define RDETH_DETH_RETH                 4
#define RDETH_AETH_PAYLD                5
#define RDETH_PAYLD                     6
#define RDETH_AETH                      7
#define RDETH_AETH_ATOMICACKETH         8
#define RDETH_DETH_ATOMICETH            9
#define RDETH_DETH                      10
#define DETH_PAYLD                      11
#define DETH_IMMDT_PAYLD                12
#define PAYLD                           13
#define IMMDT_PAYLD                     14    /* SEND + 立即数 */
#define RETH_PAYLD                      15    /* RDMA WRITE + RETH + 载荷 */
#define RETH_IMMDT_PAYLD                16
#define RETH                            17
#define AETH_PAYLD                      18    /* 应答 + AETH + 载荷 */
#define AETH                            19
#define AETH_ATOMICACKETH               20
#define ATOMICETH                       21
#define IETH_PAYLD                      22
#define DCCETH                          23
#define FETH_RETH                       24
#define RDETH_FETH_RETH                 25
#define RDETH_RETH_PAYLD                26

这张表是"抓包能读多深"的决定性因素——它决定了解析器在 BTH 之后会读哪几个头、每个头多长。源码里每个组合都对应一组 packetLength -= N; 减法,注释标明减的是哪个头。举三个实务中最常遇到的:

  • RETH_PAYLD(15)——RDMA WRITE 之后是 RETH(16 字节)再接载荷。RETH 三个字段:infiniband.reth.va(Virtual Address,FT_UINT64)、infiniband.reth.r_key(Remote Key,FT_UINT32)、infiniband.reth.dmalen(DMA Length,FT_UINT32)
  • AETH_PAYLD(18)——应答之后是 AETH(4 字节,源码 parse_AETH() 里 proto_tree_add_item(parentTree, hf_infiniband_AETH, tvb, local_offset, 4, ENC_NA),即 syndrome 之后紧跟 3 字节无格式数据)再接载荷。AETH 的 infiniband.aeth.syndrome 被三个掩码拆成三段,源码有完整定义与取值表:
  #define AETH_SYNDROME_RES     0x80    /* syndrome 最高位:保留 */
#define AETH_SYNDROME_OPCODE  0x60    /* syndrome 的 OpCode 段掩码 */
#define AETH_SYNDROME_VALUE   0x1F    /* syndrome 的数值段掩码 */

#define AETH_SYNDROME_OPCODE_ACK     0   /* Ack      —— 出错或需应答 */
#define AETH_SYNDROME_OPCODE_RNR_NAK 1   /* RNR Nak   —— 接收方暂未准备好 */
#define AETH_SYNDROME_OPCODE_RES     2   /* Reserved */
#define AETH_SYNDROME_OPCODE_NAK     3   /* Nak      —— 不可重试的错误 */

aeth_syndrome_opcode_vals[] = {
    { AETH_SYNDROME_OPCODE_ACK,     "Ack"},
    { AETH_SYNDROME_OPCODE_RNR_NAK, "RNR Nak"},
    { AETH_SYNDROME_OPCODE_RES,     "Reserved"},
    { AETH_SYNDROME_OPCODE_NAK,     "Nak"},
    { 0, NULL}
};

这四个 syndrome opcode 是排障的直接入口:看到 RNR Nak 说明对端信用不足(接收方没准备好),看到 Nak 说明是不可重试的硬错误,两者的处置方向完全不同。这也解释了为什么 AETH 的 syndrome 位在源码里被单独拆成一个 FT_UINT8 字段(读 1 字节,ENC_BIG_ENDIAN),而不是留作原始字节。

  • IMMDT_PAYLD(14)——带立即数的 SEND 之后是 IMMDT(4 字节),再接载荷
为什么 WRITE 找不到 AETH 是正常表现

这是抓包新手最容易误判的一点:抓包只看到一串 0x0A(RDMA WRITE)而中间没有 0x11(Acknowledge),会以为丢了应答。

机制上的解释是:成功完成的 RDMA WRITE 不需要逐个应答,因此抓包里 WRITE 密集出现而 Acknowledge 稀疏甚至没有,是设计使然而非抓包工具的缺陷。反过来,RDMA READ Request(0x0C)必须拿到 response 才能完成,所以 READ 在抓包里天然一问一答成对出现。

由此得到一条可操作的判读规则:"某个 WRITE 后面紧跟着一条带 AETH 的报文"本身就是异常信号——因为正常成功路径上那里不会有 AETH,出现 AETH 说明这条 WRITE 出错了,此时应直接看 aeth.syndrome.opcode 区分是 RNR Nak(信用问题,可重试)还是 Nak(硬错误,不可重试)。

5.5 三个必须在 Wireshark 里亲眼看一次的对照

光看字段表不够,下面三个对照是本节最该动手验证的部分,它们把 5.1 与 5.2 的知识变成可操作的判读能力。

对照一:同一条 WRITE 在不同传输服务段的四个操作码。分别过滤 infiniband.bth.opcode == 0x0A、== 0x2A、== 0x4A、== 0xAA,你会看到语义相同的动作,但它们的可靠性行为完全不同——0x0A 属于 RC 段、有重传与应答语义,0x4A 属于 RD 段,0xAA 属于 XRC 段。

对照二:一条 WRITE 与一条 READ 的应答形态。用 infiniband.bth.opcode == 0x0A 与 == 0x0C 各过滤一次,观察 READ 后面是否紧跟 response。按 5.4 的机制,READ 成对出现,WRITE 不成对。

对照三:lrh.lnh 取 2 与取 3 时地址列的变化。过滤 infiniband.lrh.lnh == 2 与 == 3,对比两组的源/目的列。子网内是 LID 数字(set_address 传 sizeof(uint16_t)),跨子网是 GID 地址(set_address_tvb 传 GID_SIZE)——这一个字段就回答了"这条报文有没有过路由器"。

六、ibdump 内部机制:嗅探流、缓冲环与优雅退出

6.1 它是怎么把包"挂"在 HCA 上的

第三节说 IB 路径上没有协议栈可挂,那么 ibdump 究竟怎么拿到包?答案在 verbs 层:它创建一个嗅探 QP,把 HCA 上的流量以完成事件(Completion)形式送到这个 QP。

源码里有三处证据。第一处是 QP 的访问权限被显式设为嗅探:

  attr.qp_access_flags = IBV_ACCESS_SNIFFER;

第二处是流量类型被设为嗅探流,ibdump.c 里按编译选项在两个常量间选择:

  #ifdef LIBS_EXP
    flow_attr.type = IBV_EXP_FLOW_ATTR_SNIFFER;
#else
    flow_attr.type = IBV_FLOW_ATTR_SNIFFER;
#endif

第三处是它需要通过 icmd(管理命令)接口打开端口嗅探开关——源码为此定义了两个平台相关的结构体别名:connectib_icmd_set_port_sniffer(MFT 路径)与 ibdump_icmd_set_port_sniffer(MSTFLINT 路径),然后 memset 后填字段下发。

这三处解释了两件事。其一,为什么 IB 抓包必须依赖特定固件版本——端口嗅探开关是固件侧的功能,所以才有 4.5 节那三个 MIN_FW_REQUIRED_* 版本门槛;驱动与用户态库只是把开关打开,真正干活的是固件。其二,为什么 ETH 模式的门槛低得多且只支持 RX——MIN_FW_REQUIRED_ETH 的注释写明 "This is the initial version with sniffer rules support. RX only.",说明以太网侧的嗅探规则支持是较早加入的,且只覆盖接收方向。

6.2 缓冲环:条目数与内存的真实代价

嗅探 QP 收到包后,ibdump 需要一块内存存放。源码的做法是只分配一块连续内存,然后让所有条目都指向它,注释写得很清楚:

  /* allocate the memory buffer that will hold the data */
/* To save resources, only 1 buffer is allocated (in buf[0])
 * buf[1..entries_num-1] points to this mem block
 * To save fime / mem acceses, a space for the pcap+erf headers
 * is saved before the pkt data
 */
tmp = malloc(res->entry_size * config.entries_num + 0x1000); /* Add 4KB to ensure MR is page aligned */

而每个条目的大小由端口 MTU 决定,这段分支逻辑值得记住:

  mtu = mtu_enum_to_num(res->port_attr.active_mtu);

if (config.jumbo_mtu) {
    res->entry_size = 1024 * 16;
} else {
    if (mtu <= 2 * 1024) {
        /* Seem that sniffer QP SGEs should be page aligned.
         * TODO: Check why. */
        res->entry_size = 1024 * 4;
    } else if (mtu <= 4 * 1024) {
        res->entry_size = 1024 * 8;
    } else {
        fprintf(stderr, "-E- MTU > 4KB is not supported (port is configured to %d bytes MTU)\n", mtu);
        return 1;
    }
}

把这两段代码和默认值放在一起,内存账就算清了。默认 entries_num = 4096,条目大小在 MTU ≤ 2 KB 时是 4 KB、MTU ≤ 4 KB 时是 8 KB。所以默认配置下的接收缓冲约为 4096 × 4 KB = 16 MB(2 KB MTU)或 4096 × 8 KB = 32 MB(4 KB MTU)。这正好解释了 usage() 里那句 "Each entry takes ~ MTU bytes of memory" 的实际含义——用 -b 把指数翻倍,内存也跟着翻倍。另外注意 -j / --jumbo-mtu 会直接把条目固定为 16 KB,此时 4096 条目就是 64 MB。

还有两条与内存布局相关的实现细节:

  • 条目大小是按页对齐的。源码注释 "Seem that sniffer QP SGEs should be page aligned. TODO: Check why."——这条 TODO 还在,说明对齐要求的原因尚未查清,但要求本身是明确的
  • 每条目前预留了 pcap + ERF 头空间。注释 "a space for the pcap+erf headers is saved before the pkt data",所以包数据不是从条目首地址开始的——这也是 8.6 讲链路类型时 ERF 头必须存在的原因

6.3 环形索引与"收一个补一个"

条目是按环形复用的,索引靠位与运算回绕:

  entries_mask = (config.entries_num - 1);
...
idx = (idx + added_packets) & entries_mask;

这个 & entries_mask 只有在 entries_num 是 2 的幂时才正确回绕——而源码正好执行 config.entries_num = 1 << config.log2entries_num;,所以 -b 只能收指数这件事不是约定,而是这套位运算的前提。传 -b 4096 得到的不是 4096 条目而是 2^4096,而 2^4096 条目乘以条目大小会直接导致分配失败。

拿到一个包之后,ibdump 立刻补投一个新的接收请求(Receive Request)。post_receive() 的实现很短:

  static int post_receive(struct resources *res, int idx)
{
    struct ibv_recv_wr  rr;
    struct ibv_sge       sge;
    struct ibv_recv_wr *bad_wr;
    int rc;

    /* prepare the scatter/gather entry
     * Reserve room for the pcap+erf headers before the packet data */
    memset(&sge, 0, sizeof(sge));
    sge.addr  = (uintptr_t)(res->buf[idx]);
    sge.length = res->entry_size;
    sge.lkey   = res->mr->lkey;

    /* prepare the RR */
    memset(&rr, 0, sizeof(rr));
    rr.next     = NULL;
    rr.wr_id    = 0;
    rr.sg_list  = &sge;
    rr.num_sge  = 1;

    /* post the Receive Request to the RQ */
    rc = ibv_post_recv(res->qp, &rr, &bad_wr);
    if (rc) {
        fprintf(stderr, "-E- failed to post RR\n");
        return 1;
    }
    return 0;
}
"收到一个就补一个"是维持捕获窗口恒定的必要条件

把上面的循环和 6.2 的环形索引放在一起看,补投时机就成了抗突发能力的决定因素。

如果改成"收到一批再补一批",那么在两次批量之间的空隙里,空的接收请求数量会随已收包数单调下降;如果这期间到达的包数超过了剩余空请求数,超出的包就被网卡直接丢弃,而且 ibdump 无从知晓。逐个补投则保证任意时刻空的接收请求数量都维持在 entries_num 附近,捕获窗口不会因为处理速度而收窄。

这解释了 README 那条已知问题的真正含义。"ibdump may encounter packet drops upon a burst of more than 4096 (or 2^max-burst) packets"里的 4096,指的是在没有补投发生的那段时间里能容纳的突发量。单线程的"取一个、补一个"必然有延迟,延迟窗口内到达的包如果超过剩余空请求就会丢。而"丢包不被报告"这一点是这个设计的直接后果——被网卡丢弃的包从未进入完成队列,ibdump 根本没有机会知道它存在过。

所以提高抗突发能力的方向是明确的:让补投更快,或者让空请求更多。前者对应 -T(连续内存页,减少映射开销)与 -p(独立写盘线程,把写盘从补投路径上摘出去);后者对应 -b(加大条目数)与 -M(全内存模式,抓完再落盘)。四个选项在这一节全部对上了号,没有一个是凭直觉设计的。

6.4 优雅退出:为什么 Ctrl+C 不会损坏文件

课程说"让流量运行一段时间,用 Ctrl+C 停止 ibdump"。这句话背后的机制比"能停"更重要——它决定了文件是否完整。

源码注册了四个信号,全部指向同一个处理函数:

  static void __WIN_CDECL termination_handler(int signum)
{
    my_printf("\n\nInterrupted (signal %d) - exiting ...\n", signum);
    g_stop_sniffer = 1;
}

signal(SIGINT,  termination_handler);
#ifndef __WIN__ /* no SIGPIPE on Windows */
signal(SIGPIPE, termination_handler);
#endif
signal(SIGTERM, termination_handler);
signal(SIGHUP,  termination_handler);

这段代码给出三条实用结论。其一,停止是优雅退出而不是强杀——处理函数只是把 g_stop_sniffer 置 1,主循环检测到后正常走完清理流程,所以文件是完整的。其二,Ctrl+C(SIGINT)之外,SIGTERM 与 SIGHUP 走同一路径——所以 kill 掉进程或 SSH 会话断开都不会损坏文件。其三,SIGPIPE 也走同一路径(Windows 下没有 SIGPIPE,源码用 #ifndef __WIN__ 排除)——这条对管道模式有实际意义:下游 tshark 提前退出时,ibdump 收到的是管道断裂信号而不是崩溃,因此不会留下半个文件。

退出时还会打印一次统计行,格式取自源码的 fprintf:

  fprintf(stderr, "\nCaptured: %8" PRIu64 " packets, %8" PRIu64 " bytes\n", res.sniffed_pkts, res.sniffed_bytes);

抓包过程中还有一行 \r 覆盖式的进度行,格式相同:Captured: %8lu packets, %8lu bytes。注意两处都用了 %8 宽度对齐,所以这一行的数字是右对齐、宽度 8 的——用脚本解析输出时不要按固定偏移切。

还要注意 my_printf() 与 fprintf(stderr, ...) 的区别:提示信息走 stdout,错误与最终统计走 stderr。做自动化抓包时如果只重定向 stdout,会丢掉最终那行统计。

七、抓包实操:从前置检查到字段判读

7.1 前置检查:抓包之前必须确认的四件事

抓包失败绝大多数不是因为工具坏了,而是因为环境没准备好。官方 infiniband-diags 工具集里的 ibstat 是最直接的前置检查工具,它输出的关键字段如下:

检查项ibstat 字段期望值不满足时的现象
端口逻辑状态 State Active 端口没激活,抓不到任何包
端口物理状态 Physical state LinkUp 链路没起来,LID 可能是 65535
本地 LID Base lid 有效数值 值为 65535 表示未分配,子网管理器没在跑
链路层类型 Link layer Infiniband 显示 Ethernet 说明是 RoCE,抓到的不是 IB 报文
子网管理器 SM lid 有效数值 无 SM 则无转发表,报文出不去
链路速率 Rate 与设计一致 速率取链路上最慢设备,不一致说明有设备降速

设备名本身也要确认。源码里 -d 的帮助原文是 "use IB device (default first device found)",不指定时默认取找到的第一个设备,而"第一个"是内核枚举顺序,不保证是业务口。设备名用 ibv_devinfo -d <name> 查询,形如 mlx5_0。

本系列第十四篇的一条提醒在这里适用:mlx5_0 由驱动加载顺序决定,不携带任何型号信息——要确认型号得看 ibv_devinfo 的 vendor_part_id 或 ibstat 的 CA type。抓包前把设备名与型号对应关系记下来,否则事后无法确认"这份抓包是从哪张卡上抓的"。

7.2 一条完整的抓包流程

把前面各节串起来,一次可复现的抓包流程是这样:

  • ✔ 动作一:确认端口与抓包点——ibstat 确认 State: Active、LinkUp、Base lid 有效、Link layer: Infiniband;ibv_devinfo 确认设备名与型号。记下 Base lid,它是 7.3 判读的起点
  • ✔ 动作二:启动抓包——ibdump -d <dev> -i <port> -w <file>,核对启动输出的 Device / Physical port / Link layer 三行
  • ✔ 动作三:制造流量——运行你要观察的应用或本系列第十五篇讲的 perftest,让目标 QP 上产生流量
  • ✔ 动作四:停止并核对统计——Ctrl+C,记下 stderr 上那行 Captured: N packets, M bytes,这是 8.3 判断有无丢包的唯一数据
  • ✔ 动作五:分析——把 pcap 拷到工作站,Wireshark 打开,产出每一帧的 LRH / BTH / RETH / AETH 逐字段可读

动作四里"记下统计"这一步不要省。因为丢包不报告(8.3),抓包文件里看不出丢了多少包——唯一能与链路侧对照的量化线索就是 Captured 这一行数字,以及应用自己报告的传输字节数。

7.3 拿到文件之后:三个立刻能做的判读动作

打开 pcap 后,按下面三步走,能把绝大多数"抓到了但看不懂"的问题解决掉。

动作一:确认抓包点位置

过滤 infiniband.lrh.slid == <你的 Base lid>,如果一条都过滤不到,说明这个 LID 不是源——再试 infiniband.lrh.dlid == <你的 Base lid>,如果这个有结果,说明这台机器是这条报文的接收方而不是发送方。两条都为空才需要回头看抓包是否真的在工作。

这一步的价值在于:它把"抓包点在路径上的哪个位置"这个信息变成了可判定的。抓包只能看到流经本卡的流量,看不到交换机内部发生了什么,所以必须先知道自己站在哪一侧。

动作二:按传输服务分组看操作码

用 infiniband.bth.opcode 做统计(Wireshark 的 Statistics → Conversations 或按字段分组),先看这个 QP 上主要跑的是哪一类操作。按 5.2 的规律,0x0A(RC WRITE)密集说明这是 RDMA 数据流,0x04 / 0x64 密集说明是消息语义流量,0x11 密集说明重传或流控有问题。

动作三:顺着操作码看后续头

按 5.4 的 27 种头序组合,确定每类操作码后面应该跟什么头。如果 Wireshark 没有按预期解析出 RETH / AETH / IMMDT,那本身就是信号——可能意味着抓到的字节被截断(snaplen 不够)或链路类型不对(8.6)。

7.4 常用过滤表达式清单

下面这些表达式都基于本篇第五节已核实的字段名,可以直接粘进 Wireshark 的 Display Filter 栏:

  
# --- 按传输服务筛选(高 3 位) ---
infiniband.bth.opcode & 0xE0 == 0x00     # RC
infiniband.bth.opcode & 0xE0 == 0x60     # UD
infiniband.bth.opcode == 0x80               # CNP 拥塞通知包

# --- 按具体操作筛选 ---
infiniband.bth.opcode == 0x0A               # RC RDMA WRITE Only
infiniband.bth.opcode == 0x0C               # RC RDMA READ Request
infiniband.bth.opcode == 0x11               # RC Acknowledge
infiniband.bth.opcode == 0x64               # UD SEND only

# --- 按子网范围筛选(不查配置即可判断) ---
infiniband.lrh.lnh == 2                    # 子网内(地址列是 LID)
infiniband.lrh.lnh == 3                    # 跨子网(地址列是 GID)

# --- 按地址与队列对筛选 ---
infiniband.lrh.slid == 3                    # 源 LID
infiniband.lrh.dlid == 5                    # 目的 LID
infiniband.bth.destqp == 0x12345            # 目的 QP(默认嗅探 QP 号)

# --- 排障类 ---
infiniband.aeth.syndrome.opcode == 1        # RNR Nak:接收方信用不足
infiniband.aeth.syndrome.opcode == 3        # Nak:不可重试的硬错误
infiniband.bth.p_key == 0xFFFF              # 默认 P_Key(未分区)
  

其中 infiniband.bth.destqp == 0x12345 里的 0x12345 来自 ibdump.h 的 #define DEF_QKEY 0x12345——这是 ibdump 自己的嗅探 QP 号,在抓包文件里会看到发往这个 QP 的报文,那正是嗅探器自己收包产生的流量,不是业务流量。看到这类报文不要当成异常。

7.5 四个抗丢包选项怎么选

把 4.5 与 6.3 的结论整理成选型表。这四个选项的作用点各不相同,不能互相替代:

  • ✔ -b <log2>——加大 WQE 条目数,扩大未挂接收请求的空位数量。代价是每条目约占用一个 MTU 大小的内存,指数翻倍则内存翻倍(见 6.2 的内存账)
  • ✗ -T——使用连续内存页,减少映射开销,间接让补投更快。与 -b 正交,两者可同时用
  • ✗ -p <bytes>——独立写盘线程,把写盘从补投路径上摘出去。需要指定两个临时缓冲区的大小
  • ✗ -M <bytes>——全部缓存内存、停止时才落盘。帮助原文明确说 "It is faster than default mode (less chance for packet loss), but takes more memory. In this mode, ibdump stops after bytes are captured"——抓满指定字节数会自动停止,这是它与其它选项最大的行为差异

唯一的互斥关系是 -M 与 -p,源码在参数解析后显式检查并报错 -E- You can not use mem-mode and writer-thread simultaneously.,随后返回 1。

选型建议按"能不能接受自动停止"分两类。能用 -M 的场景(知道要抓多少字节、且能接受抓满就停)优先用它,因为帮助文本对它抗丢包效果的描述最明确;需要长时间连续抓包的就不能加 -M,此时组合是 -b 加大条目数 + -T 连续页 + -s 静默(减少终端输出开销),并按 8.3 的方法验证是否真的没丢。

八、边界、限制与排查

8.1 抓 0 字节的五步排除顺序

0 字节输出不报错是抓包最常见的正常现象,因为它完全可能抓对了设备但那个方向上没有流量。README 列出的第一条已知问题就与端口占用有关(见 8.4),而 -I 默认两个方向都开。按下面顺序排除,每一步都有明确的判断依据:

  • ✔ ① 端口状态——ibstat 必须是 State: Active、Physical state: LinkUp、Base lid 有值(不是 65535)
  • ✔ ② 端口号——双端口 HCA 上默认是 1,抓第二个口要给 -i 2,给错了抓到的就是空文件而不是报错
  • ✔ ③ 抓取方向——用 -I tx 与 -I rx 分别试。源码 case 'I': 里 tx 置 sniff_rx = 0、rx 置 sniff_tx = 0,传其它值会报 "Bad parameter for direction flag"
  • ✔ ④ 设备名——用 ibv_devinfo 确认。不指定 -d 时取的是内核枚举到的第一个设备,不保证是业务口
  • ✔ ⑤ 链路层类型——看启动输出的 Link layer,在 RoCE 卡上它显示 Ethernet,此时抓到的不是 IB 报文

如果五步都排除了仍然 0 字节,再看两条更隐蔽的可能:固件版本是否达到 4.5 节的门槛(但这种情况通常会明确报 does not support sniffing 而不是静默 0 字节),以及这个端口上是否真的有流量——子网刚激活时只有 SM 的管理流量,用 -I tx 配合制造流量的应用最容易确认。

8.2 抓包能看到什么、看不到什么

抓包能力有明确边界,这一节把边界划清楚,避免在"抓不到"上浪费时间:

  
  
抓包能看到(pcap 文件里有的)              抓包看不到(只能用性能计数器)           
------------------------------------------------------------------------------------
传输层 BTH 及其后续头(由 Opcode 决定)    物理层链路训练过程                       
网络层 LRH(LNH=3 时含 40 字节 GRH)       链路层信用(credit)计数                 
源 / 目的 LID,跨子网时为 GID              交换机内部的转发表与选路过程             
虚拟通道 VL 与服务等级 SL 的取值           ICRC / VCRC 的逐位计算与校验结果         
P_Key 分区值                               链路重试等物理层事件                     
------------------------------------------------------------------------------------
看不见                                     物理层与链路层的比特细节,pcap 里根本没有
  

这张图里最该记住的是最下面那条线:pcap 文件里没有物理层与链路层的比特细节。你在 Wireshark 里看到的是"网卡交给上层之后"的字节。所以链路是否健康、是否有符号错误、是否发生重试——这些必须用性能计数器查(perfquery 的 PortCounters / PortCountersExtended),抓包解决不了。这与本系列第十五篇讲 perftest 时的判读纪律是同一条:计数器给权威读数,抓包给字节级证据,两者互补不能互相替代。

8.3 丢包不报告:三条可操作的判据

README 的前两条已知问题必须放在一起读:"ibdump may encounter packet drops upon a burst of more than 4096 (or 2^max-burst) packets"与 "Packets loss is not reported by ibdump"。合起来就是一条让人很棘手的性质:Captured: N packets 里的 N 是"成功采集到的包数",不是"链路上传输的包数"。两个数字在丢包发生时不相等,而工具不给任何提示。

为什么不能靠"抓两次比对"来判断?因为丢包是突发相关的——两次抓包的网络状况不同,N 值本来就会不同。必须找与"完整性"直接相关的判据,下面三条按可靠性排序。

  1. 用 PSN 连续性判读,这是最可靠的一条。RC 报文带 infiniband.bth.psn,同一方向、同一 QP 的 PSN 应当连续。把抓包按 bth.psn 排序后检查是否跳号,跳号即说明中间有包没被抓到。这条判据的优势是它直接检验"包有没有少",而不是间接推断
  2. 用应用报告的字节数与抓包统计做数量级对照。如果两者差一个数量级以上,说明抓包不完整。这条只能判断"差很多"的情况,抓包漏掉几十个包时看不出来
  3. 用 -b 加大条目数重跑一次,看包数是否明显增加。如果增加了,说明上一次确实丢了。这条是三条里唯一的实验验证,但代价是要抓两遍

本节结论:抓包数据的可信度不能默认成立,必须主动验证。任何基于抓包的定量结论(比如"这条链路丢了 X% 的包")都应当附带一条完整性判据的验证过程,否则得出的数字是不可信的。

8.4 README 列出的四条已知限制

OFED README 的 "Known Issues" 一节列出四条限制,逐条给出实践含义:

README 原文实践含义能做什么
"ibdump may encounter packet drops upon a burst of more than 4096 (or 2^max-burst) packets." 突发超过捕获窗口会丢包 按 7.5 加大 -b,按 8.3 验证完整性
"Packets loss is not reported by ibdump." 丢包没有任何提示 必须用 8.3 的三条判据主动验证
"Outbound retransmitted and multicast packets may not be collected correctly." 出向重传与组播报文可能采集不全 分析重传行为不能只靠 ibdump;组播结论要对照计数器
"ibdump may stop capturing packets when run on the same port of the Subnet Manager (E.G.: opensm). It is advised not to run the SM and ibdump on the same port." 与子网管理器同端口会停止抓包 抓包必须选非 SM 端口

第四条是最容易被忽略、后果最严重的一条。如果你在跑 opensm 的那台服务器上、且打算在 SM 所在端口抓包,结果就是抓不到包——而这条限制与 4.5 节的端口号、5.1 节的链路层都无关,属于"配置对了但依然没数据"的那一类问题。所以 7.3 的动作一里把"确认抓包点"列为第一步,目的就是先把这类问题排除掉再往下走。

第五条不在 "Known Issues" 列表但同样重要,是编译期约束:不支持第五代 IB 设备(ConnectX-4 及更新)在未带 FW tools 编译时的抓包,解决办法见 4.5 节末尾的四种编译配置。

8.5 管道模式:能分析,但不取证

帮助文本里 -w 有一条容易被忽略的说明:"'-' stands for stdout - enables piping to tcpdump or tshark.",源码里对应这段逻辑:

  if (!strcmp(config.out_file_name, "-")) {
    config.to_stdout = 1;
    config.is_silent = 1;
}

所以管道用法是官方支持的:

  
# 边抓边统计:只关心操作码分布
ibdump -d mlx5_0 -w - | tshark -i - -Y 'infiniband.bth.opcode == 0x0A'

# 管道给 tcpdump(按 pcap 解析)
ibdump -d mlx5_0 -w - | tcpdump -r -
  

但这个模式有三条明确代价,选型时必须知道:

  • ✗ 代价一:分析端的处理速度会成为整条链路的速度上限。如果 tshark 的过滤比抓包慢,管道会反过来施加背压,增加丢包概率——这条把 8.3 的第一条判据又拉了回来
  • ✗ 代价二:没有文件就没有证据。事后想再查别的字段时,发现已经没得可查了。所以管道模式只适合"当下就能问完"的问题
  • ✗ 代价三:进度行被强制关闭。源码在 to_stdout 时把 is_silent 置 1,所以连 Captured: N packets 的进度行都看不到,也就无法用 8.3 的方法判断是否丢包

另外 -o 不要传 -——帮助原文明确 "Do not use - for backward compatibility",因为 -o 是历史别名,管道行为只挂在 -w 上。

8.6 一个真实的坑:链路类型不是 LINKTYPE_INFINIBAND

这一节是本篇最值得单独拿出来的一条,因为它是采集端决定兼容性的最好例证。

ibdump 写 pcap 文件头时,链路类型的选择逻辑是:

  /* pcap header erf settings */
if (config.with_erf == -1) {
    if (config.is_eth) {
        config.with_erf = 0;
    } else {
        config.with_erf = 1;
    }
}

/* NOTE: Assuming no ERF is eth. Currently no DLT_INFINIBAND */
hdr.network = config.with_erf ? DLT_ERF : DLT_EN10MB;

对照 ibdump.h 的宏定义与官方链路类型登记表:

常量值含义出处
DLT_EN10MB 1 以太网 ibdump.h
DLT_ERF 197 ERF 伪头,IB 模式实际写入这个 ibdump.h,官方登记 LINKTYPE_ERF
ERF_TYPE_INFINIBAND 21 ERF 记录类型,标识记录里装的是 IB 流量 ibdump.h
LINKTYPE_INFINIBAND 247 规范确实为 IB 登记了这个,但 ibdump 不用它 tcpdump.org 官方登记表

源码注释里那句 "NOTE: Assuming no ERF is eth. Currently no DLT_INFINIBAND" 说明了原因:Wireshark 侧当时还没有 DLT_INFINIBAND 的读取实现,所以 ibdump 借用了 ERF 通道。Wireshark 的 wiretap/erf.c 里 ERF_TYPE_INFINIBAND 被显式列为支持类型,读取 ERF 记录后按类型 21 分派给 IB 解析器。

这条设计带来一个直接后果:--erf 0 在 IB 模式下会直接失败。源码检查原文是:

  if (!config.is_eth && !config.with_erf) {
    /* IB must have an ERF header. */
    fprintf(stderr, "-E- Can not dump Infiniband traffic without an ERF header (--erf 0)\n");
    return -1;
}

报错信息把原因讲得很直接:"IB must have an ERF header."——去掉 ERF 头后产出的就是"链路类型标成以太网、内容却是 IB 报文",Wireshark 会拿以太网解析器去解 LRH 开头的字节。

两条实践推论。其一,不要手工改 pcap 的链路类型字段——即使把 197 改成 247 也不能让 Wireshark 正确解析,因为 ERF 记录头还在,字节布局根本不是规范定义的 IB 裸报文。其二,不要看到 247 就以为是 ibdump 产出的文件——ibdump 产出的是 197。反过来,如果拿到一个链路类型是 197 的文件,Wireshark 能正确显示 infiniband.* 字段,那基本可以确认它来自 ibdump 或同类工具。

最后补一条与抓包对象相关的边界:ibdump 靠 HCA 队列取数据,而软 RoCE(rxe)是内核软件实现,没有真实的硬件嗅探路径——抓这类流量走的是普通网卡的 pcap 通道,字段解析路径与本篇第五节讲的硬件 IB 完全不同。

8.7 抓包的性能开销

本节只给出定性结论,不给出任何数字。可以确定的是开销来自三处:① 接收缓冲占用的内存(6.2 已算出默认 16–32 MB,-b 或 -j 会显著放大);② 每个包都要做的补投与写盘操作,这部分消耗 CPU 与 I/O;③ 打开端口嗅探开关本身对转发路径的影响。

至于"占用多少、对业务时延影响多大",官方文档没有给出可引用的数字,必须在目标环境里独立测量。这是一条应当在生产环境里验证的指标,而不是可以照搬的结论——不同 HCA 代次、不同 MTU、不同条目数下开销差异很大。

实践上可控的几条:用 -q 只看关心的源 QP、用 -I 把方向收窄到单向、必要时用 -M 限定总字节数让抓包自动停止,都能减少无谓开销。但要注意 -q 与 -I 收窄的是"抓什么",不改变"抓包本身要付出的固定成本"——缓冲区的分配(6.2 的 16–32 MB)在启动时就已完成。

九、FAQ 高频问答(20 组)

本节围绕 InfiniBand 流量抓取与分析的 20 个高频易错点展开。题目覆盖工具定位、格式细节、字段判读、限制取舍四类,答案中的命令选项、默认配置值、错误文案、位掩码与操作码取值均引自文末「参考资料」列出的源码文件与官方文档。

  1. Q1. 抓包具体能做什么?谁在用?

    抓包允许截获流经网络的数据包,并以人类可读的格式呈现出来,让 IT 团队能识别问题并解决影响日常运作的网络故障。课程列出四类使用者:网络管理员用它排查网络问题,网络安全工程师用它检查安全问题,开发者用它调试协议实现,教师与学生用它教学或学习网络协议。

    四类角色的判读层次不同:管理员关注可达性与重传,安全工程师关注 P_Key 与会话标识,开发者关注 PSN 与 AETH,师生关注逐字段位域划分——同一份文件,四种取法。

  2. Q2. 什么是网络报文分析器?和 sniffer 是同一个东西吗?

    同一个东西的两种叫法。课程的定义是:网络报文分析器或嗅探器捕获数据包,然后尽可能详细地显示包数据。注意这个定义里有两个动作——捕获与 显示——它们的技术难度完全不同,因此工业界事实上把"采集器"与"分析器"分成了两类工具。课程随后把 tcpdump / windump 归为"生成 pcap 文件的采集器",把 Wireshark 归为"可以打开这些文件"的工具,这个归类就是按那条分界线划的。

  3. Q3. TCP dump 和 windump 是什么关系?

    windump 是 TCP dump 的 Windows 版本。课程给 tcpdump 的定义是"一个常见的报文分析器,它允许显示 TCP / IP 数据包以及通过本机所连网络传输的其他数据包"。两者都生成一个已知为 pcap 格式的文件,用 pcap 扩展名标识。所以它们是可互换的:在 Windows 上用 windump 抓的文件,拿到 Linux 上用 Wireshark 打开没有任何问题——这正是 pcap 作为跨平台格式的价值。

  4. Q4. Wireshark 是什么?有什么特点?

    Wireshark 是最广泛使用的报文分析器之一,它是免费且开源的,可以用打开 tcpdump 或 windump 抓到的文件,是 Web UI 基础的,并且有许多集成的排序与过滤选项。课程还说:如果你熟悉 Wireshark 分析 TCP / IP 流量,会发现用它分析 InfiniBand 流量同样非常容易且方便;如果是 Wireshark 新手,它的官方网站是一个好的起点。

    这句"容易方便"成立的前提是抓包文件里字节完整——界面能显示什么由采集端决定,不由分析端决定。

  5. Q5. 为什么 tcpdump 和 windump 抓不到 InfiniBand 流量?

    因为它们只捕获 TCP / IP 数据包,而 InfiniBand 数据包绕过了协议栈。这条因果链是本篇的核心。技术含义是:tcpdump 靠挂接 TCP/IP 协议栈取数据,IB 报文不经过那条栈,所以取不到。

    技术细节是:IB 报文以 LRH 开头,网络层信息直接编码在 LRH 里,不需要 IPv4 / IPv6 头;传输层以 BTH 开头,DestQP 直接编码目的队列对,不需要 TCP / UDP 端口号——IB 不是"另一种 IP",而是与 IP 并列的另一套端到端协议。因此遇到"抓不到"时,正确方向是换工具,不是改过滤表达式——过滤表达式是抓到手之后才生效的。

  6. Q6. ibdump 是什么?输出什么格式?

    ibdump 是 MLNX_OFED 包含的工具,捕获流经 InfiniBand 端口、并从该端口流出的 InfiniBand 流量。它生成的 dump 文件以 pcap 格式创建,因而可以被 Wireshark 之类的支持工具查看。

    OFED README 补充:它"提供与以太网上 tcpdump 工具类似的功能","转储流经 Mellanox 适配器卡的 IB 流量","使用户能分析网络行为与性能、并调试发送或接收 IB 流量的应用"。另有一条实用信息:虽然 ibdump 是 Linux 应用,生成的 pcap 文件可以在任意操作系统上分析。

  7. Q7. ibdump 的命令怎么写?主要参数各管什么?

    ibdump -d <hca> -w <file>。课程明确讲了两个参数:用 -d 标志加上 HCA 名字,来选择要为其抓包的相关设备或 HCA;可以加上 -w 标志加上文件名,把数据保存到特定文件,默认文件名是 sniffer.pcap,在当前工作目录创建。

    源码里还有课程没提但很重要的:-i / --ib-port= 指定端口,默认值是 1。双端口 HCA 上抓第二个口必须显式给 -i 2,否则抓到的是空数据而不是报错。

  8. Q8. -d 不指定会怎样?设备名怎么确认?

    源码里 -d / --ib-dev= 的帮助原文是"use IB device (default first device found)"——不指定时默认取找到的第一个设备。但"第一个"是内核枚举顺序,不保证是业务口,所以实践上应显式指定。

    设备名用 ibv_devinfo -d <name> 查询,形如 mlx5_0。注意本系列第十四篇的提醒:mlx5_0 由驱动加载顺序决定,不携带任何型号信息——要确认型号看 CA type 或 vendor_part_id。

  9. Q9. 怎么停止抓包?Ctrl+C 会不会损坏文件?

    让流量运行一段时间,用 Ctrl+C 停止 ibdump。这是优雅退出,不是强杀,所以文件完整。源码里 Ctrl+C 对应的 SIGINT 由 termination_handler 处理,它打印 Interrupted (signal %d) - exiting ... 并把 g_stop_sniffer 置 1,主循环检测到后正常走完清理流程。

    另外还注册了 SIGTERM、SIGHUP 与(Windows 下的 SIGPIPE 除外)走同一路径,所以 SSH 断连不会损坏文件。退出时还会打印一次 Captured: N packets, M bytes,注意这行走的是 stderr。

  10. Q10. 抓到的包头有 LRH、BTH 和 ETH,这个说法对吗?

    课程说能看到被抓到的 IB 数据包"带着各自的头部:LRH、BTH 和 ETH",准确说法是:每一帧都有 LRH(Local Route Header)与 BTH(Base Transport Header),ETH 只在需要额外传输头信息时才出现。

    更准确地说,转写里的 "ETH" 是 AETH(ACK Extended Transport Header)的残缺,它只在需要应答的操作码上出现,并非固定的第三个头。按"三个头必备"去理解,会在 WRITE 请求里找不到 AETH 而误判为抓包不完整。此外抓包文件里看不到物理层与链路层的比特细节——链路训练、信用计数、ICRC / VCRC 的逐位计算都不在文件里。

  11. Q11. LRH 有哪些字段?位域怎么切?

    LRH 固定 8 字节,含六个字段。按字节排列:Virtual Lane(字节 0 高 4 位,掩码 0xF0)/ Link Version(字节 0 低 4 位,掩码 0x0F)/ Service Level(字节 1 高 4 位,掩码 0xF0)/ Reserved(字节 1 的 bits 3:2,掩码 0x0C)/ Link Next Header(字节 1 低 2 位,掩码 0x03)/ Destination LID(字节 2:3)/ Reserved(掩码 0xF800)/ Packet Length(掩码 0x07FF)/ Source LID(字节 6:7)。

    三条必须记住的细节:① Packet Length 的单位是 4 字节字,源码是 packetLength = packetLength * 4,注释写明 "Multiply by 4 to get true byte length. This is by specification.",所以显示 1024 实际是 4096 字节;② 它包含 LRH 自身,源码紧接着执行 packetLength -= 8; 注释 "Shave 8 bytes for the LRH.",之后还会按头序继续减 GRH 40 字节与各后续头;③ 源与目的被写进 Wireshark 地址列,类型是 AT_IB,所以看到的是 LID 数值不是 IP 地址。

  12. Q12. BTH 有哪些字段?

    BTH 固定 12 字节。字段为:Opcode(FT_UINT8)/ Solicited Event(FT_BOOLEAN,0x80)/ MigReq(FT_BOOLEAN,0x40)/ Pad Count(掩码 0x30)/ Header Version(掩码 0x0F)/ P_Key(16 位)/ DestQP(FT_UINT24)/ Acknowledge Request(FT_BOOLEAN,0x80)/ Reserved(7 bits,掩码 0x7F)/ PSN(FT_UINT24)。

    解读的关键是:操作码的高 3 位直接编码传输服务,源码宏定义为 TRANSPORT_RC = 0 / TRANSPORT_UC = 1 / TRANSPORT_RD = 2 / TRANSPORT_UD = 3,注释写明 "The values match the corresponding 3 bits of the opCode field in the BTH"。

  13. Q13. 为什么同一个 WRITE 有四个操作码?

    因为传输服务编在操作码高 3 位,8 位操作码被切成四段、每段 32 个,所以"RDMA WRITE Only"这个语义相同的动作,在四段里各占一个位置:0x0A(RC)/ 0x2A(UC)/ 0x4A(RD)/ 0xAA(XRC)。

    实践含义有两条:① 不能脱离传输服务谈数值,纯按数值过滤会把不同传输服务的报文混在一起;② 看到操作码就已经知道这条报文走哪种可靠性语义——RC 段有重传与应答,UD 段发出去就不管,所以判断"这个操作为什么丢了"要先看它属于哪一段。

  14. Q14. 怎么区分子网内和跨子网流量?

    看 infiniband.lrh.lnh 的值就能区分这个包是子网内还是跨子网,不需要查任何配置。源码在读完 LRH 第二个字节后把低 2 位存入 lnh_val,再用一个 switch 决定下一步。四个宏是 RAW = 0 / IP_NON_IBA = 1 / IBA_LOCAL = 2 / IBA_GLOBAL = 3。

    LNH = 2(IBA_LOCAL)后面直接是 BTH,是子网内通信;LNH = 3(IBA_GLOBAL)后面先跳过 40 字节 GRH 再读 BTH,此时 Wireshark 的源/目的列会被改写成 128 位 GID 地址——源码里子网内用 set_address(..., AT_IB, sizeof(uint16_t), ...)、跨子网用 set_address_tvb(..., AT_IB, GID_SIZE, ...)。

  15. Q15. BTH 之后 Wireshark 会读什么?

    BTH 之后解析器怎么走完全由操作码决定,源码为此维护了一张头序枚举表,源码注释写明"These are simply an enumeration of the possible header combinations defined by the IB Spec.",共 27 个具名组合(编号 0–26)。

    最常遇到的三个:RETH_PAYLD(15,RDMA WRITE 之后是 RETH 再接载荷)/ AETH_PAYLD(18,应答之后是 AETH 再接载荷)/ IMMDT_PAYLD(14,带立即数的 SEND 之后是 IMMDT)。RETH 三个字段是 infiniband.reth.va(FT_UINT64)、infiniband.reth.r_key(FT_UINT32)、infiniband.reth.dmalen(FT_UINT32)。

  16. Q16. 为什么 WRITE 后面没有 AETH?READ 后面却有?

    抓包里一串 0x0A(RDMA WRITE)而中间没有 0x11(Acknowledge)是正常表现,不是丢了应答;而 RDMA READ Request(0x0C)必须拿到 response,所以 READ 在抓包里天然一问一答成对出现。

    也就是说,成功完成的 RDMA WRITE 不需要逐个应答。这个不对称性带来一条可操作的判读规则:"某个 WRITE 后面紧跟着一条带 AETH 的报文"本身就是异常信号——正常成功路径上那里不会有 AETH,出现 AETH 说明这条 WRITE 出错了,此时应直接看 infiniband.aeth.syndrome 区分错误类型。

  17. Q17. AETH 的 syndrome 怎么读?

    AETH 里的 syndrome 被三个掩码拆成三段,源码有完整定义:AETH_SYNDROME_RES 0x80(保留)、AETH_SYNDROME_OPCODE 0x60(OpCode 段)、AETH_SYNDROME_VALUE 0x1F(数值段)。

    OpCode 段有四个具名取值:AETH_SYNDROME_OPCODE_ACK 0 = "Ack"、AETH_SYNDROME_OPCODE_RNR_NAK 1 = "RNR Nak"、AETH_SYNDROME_OPCODE_RES 2 = "Reserved"、AETH_SYNDROME_OPCODE_NAK 3 = "Nak"。这四个值是排障的直接入口:RNR Nak 说明对端信用不足(可重试),Nak 说明是不可重试的硬错误,两者的处置方向完全不同。Wireshark 里对应过滤字段是 infiniband.aeth.syndrome.opcode。

  18. Q18. 抓包抓到 0 字节,怎么排查?

    0 字节输出不报错是抓包最常见的正常现象,因为它完全可能抓对了设备但那个方向上没有流量。按这个顺序排除:① 端口状态——ibstat 必须是 State: Active 且 Physical state: LinkUp、Base lid 有值(不是 65535);② 端口号——双端口 HCA 上默认是 1,抓第二个口要给 -i 2;③ 抓取方向——用 -I tx|rx 试两侧(源码里 tx 置 sniff_rx = 0、rx 置 sniff_tx = 0,传其它值报 "Bad parameter for direction flag");④ 设备名——用 ibv_devinfo 确认;⑤ 链路层类型——看启动输出的 Link layer,在 RoCE 卡上它显示 Ethernet,此时抓到的不是 IB 报文。

    还有一条容易漏的:不要在子网管理器所在端口上抓包——README 明确说 "ibdump may stop capturing packets when run on the same port of the Subnet Manager (E.G.: opensm)."

  19. Q19. 抓包会丢包吗?怎么知道有没有丢?

    会。README 第一条已知问题是"ibdump may encounter packet drops upon a burst of more than 4096 (or 2^max-burst) packets",第二条是"Packets loss is not reported by ibdump"——所以 Captured: N packets 里的 N 是"成功采集到的包数",不是"链路上传输的包数"。两个数字在丢包发生时不相等,而工具不给任何提示。

    三条可操作的判据:① 用 RC 报文按 PSN 排序后检查连续性,出现跳号即说明中间有包缺失(最可靠,直接检验"有没有少");② 用应用自己报告的传输字节数与抓包统计的字节数量级对照,差一个数量级以上就说明抓包不完整;③ 用 -b 加大条目数重跑一次,如果包数明显增加,就说明上一次确实丢了。机制上,被网卡丢弃的包从未进入完成队列,ibdump 没有机会知道它存在过。

  20. Q20. ibdump 的 pcap 链路类型到底是多少?为什么不用 LINKTYPE_INFINIBAND?

    ibdump 产出的 pcap 链路类型是 DLT_ERF = 197(规范里登记为 LINKTYPE_ERF),并把 ERF 记录类型置为 ERF_TYPE_INFINIBAND = 21;而不是规范里确实为 IB 登记的 LINKTYPE_INFINIBAND = 247。源码在设置链路类型时有一句注释原文:"NOTE: Assuming no ERF is eth. Currently no DLT_INFINIBAND"。

    所以 --erf 0 在 IB 模式下会直接失败,原文报错是 Can not dump Infiniband traffic without an ERF header (--erf 0)——因为去掉 ERF 头后产出的就是"链路类型标成以太网、内容却是 IB 报文",Wireshark 会拿以太网解析器去解 LRH 开头的字节。实践推论:不要手工改 pcap 的链路类型字段,也不要看到 247 就以为是 ibdump 产出的文件。

FAQ 总纲(口诀式速记)

  • 1 个定义:抓包 = 截获数据包并以人类可读格式呈现;4 类使用者:管理员排障 / 安全工程师查安全 / 开发者调试实现 / 师生学协议
  • 3 个工具:tcpdump(类 Unix 采集器)/ windump(它的 Windows 版)/ Wireshark(免费开源的分析器,能打开前两者的文件)
  • 1 个格式:三者都产出或消费 .pcap;文件即契约,因此采集与分析可以跨机器跨操作系统
  • 1 条根因:IB 报文绕过协议栈(LRH 里直接编码网络层、BTH 里直接编码 DestQP,IB 与 IP 是并列的两套端到端协议)→ 通用嗅探器失效
  • 1 个工具:ibdump 抓流经与流出 HCA 端口的 IB 流量,产出 pcap 且可在任意 OS 上分析
  • 2 个默认:文件名 sniffer.pcap(当前目录)/ 端口 1(双端口 HCA 抓第二个口必须 -i 2)/ 条目 4096(log2 = 12)
  • 3 个易错点:-b 收 log2 不是条目数 / -M 与 -p 互斥 / -o 不接受 -
  • 1 个格式决定:IB 写 DLT_ERF = 197 + ERF 类型 21,不用已登记的 LINKTYPE_INFINIBAND = 247,故 --erf 0 在 IB 模式直接失败
  • 3 条硬约束:IB 固件需 2.33.5000(RC 为 2.33.1330)/ 勿与 SM 同端口 / ConnectX-4 及更新设备需带 FW tools 编译
  • 2 个必备头:LRH 8 字节(六字段,PktLen 单位是 4 字节字且含 LRH 自身)/ BTH 12 字节(含 Opcode / P_Key / DestQP / PSN)
  • 1 个位域规律:Opcode 高 3 位 = 传输服务(RC 0 / UC 1 / RD 2 / UD 3),同一个 WRITE 在四段里是 0x0A / 0x2A / 0x4A / 0xAA
  • 1 个纯抓包判据:lrh.lnh == 2 子网内 / lrh.lnh == 3 跨子网(源目的列变 GID)
  • 27 种头序列:由操作码决定 BTH 之后读什么;WRITE 成功不回 AETH,READ 天然一问一答
  • 4 条已知限制:突发丢包 / 丢包不报告 / 出向重传与组播可能不全 / 勿与 SM 同端口
  • 3 条能力边界:链路层以下不可见(查性能与状态)/ 丢包不报告(缺失的包有两种解释)/ 抓包点在路径上(只看到流经本卡的流量,且占用资源)
  • 1 条判读动作链:用 LID 确认抓包点 → 按 opcode 分组看传输服务 → 顺操作码看 RETH / AETH / IMMDT

十、Roadmap 后续预告

本篇是 InfiniBand 专题的第十六篇。它的位置很明确:本系列第八篇《子网初始化:拓扑发现、LID 分配、路径计算与子网激活》讲清了子网是怎么被建立起来的,本系列第十四篇《HCA 固件升级:工具链、版本识别、固件烧录与验证》讲清了每张卡最初是怎么被点亮的,本篇讲清了怎么用抓包把这两件事的结果验证下来。配置与验证构成闭环——只配置不验证,等于不知道自己配得对不对。

接下来的方向,按由"抓包验证"向"运行期观测"深入的顺序包括:

  • 性能计数器与抓包的对照:本篇 8.2 的能力边界里,链路层以下不可见这一条需要另一类工具补齐。perfquery 的 PortCounters / PortCountersExtended 提供了包数、字节数、丢弃计数、符号错误计数的权威读数,与抓包统计做交叉验证,正好解决 8.3 那条"如何确认抓包完整"的问题
  • 交换机侧的抓包:端口镜像与 RSPAN:本篇只讲了在 HCA 端口上抓包,而抓包点的位置决定了能看到什么——这是 7.3 动作一与 8.2 能力边界的共同根源。ibdump 的 --decap 选项就是为 RSPAN 流量准备的(帮助原文 "Decapsulate port mirroring headers. Should be used when capturing RSPAN traffic."),能让抓包点脱离业务路径,代价是镜像口本身的带宽限制
  • 拥塞控制报文的抓取与分析:bth_opcode_tbl 里有 0x80 = CNP,这是本篇列出的操作码里唯一一个与拥塞控制直接相关的,且它独立占用一个段、不属于 RC/UC/RD/UD 任何一段。CNP 在抓包里长什么样、由什么条件触发、与 ECN 阈值如何配合,属于运行期调优的必备知识
  • 信用环与 AETH 详细判读:本篇 5.4 提到 AETH 的 syndrome 是排障入口,但 RNR Nak 与 Nak 各自对应哪些具体错误、哪些可重试哪些不可重试,需要逐个 syndrome 取值展开。这与本系列第十篇讲的信用环直接相关
  • 抓包的性能开销量化:本篇 1.3 与 8.7 都提到"抓包会占用资源",但占用多少、对业务时延影响多大,官方文档没有给出数字,需要独立测量。这是一个应当在生产环境里独立验证的指标,而不是可以引用的结论
  • 软 RoCE 与硬件 IB 的抓包差异:ibdump 靠 HCA 队列取数据,而 rxe / Soft-RoCE 是内核软件实现,没有真实的硬件嗅探路径。抓这类流量需要走普通网卡的 pcap 通道,字段解析路径完全不同
  • 多端口抓包的并发与文件管理:ibdump 一次只抓一个设备一个端口。要同时观测多个节点或多个方向,只能开多个进程,此时文件命名、时间对齐、以及"多份抓包之间如何判断是同一次故障"都是需要自己建立规范的部分
  • 用抓包验证 QoS 与 SL-to-VL 映射:本系列第八篇讲了 SL-to-VL 映射表与 VL 仲裁的结构与编程,本篇给了读 lrh.vl 与 lrh.sl 的观测手段,但把两者合起来形成"策略是否真的生效"的验证方法,还没有讲

如果你在实践中遇到具体问题——例如 ibdump 报 Device firmware version ... does not support sniffing、--erf 0 被拒绝、启动时 Link layer 显示 Ethernet 而抓不到 IB 包、抓了几十万个包但发现 PSN 有跳号——欢迎在评论区留言,我会挑出共性问题单独扩展一篇专题。

posted @ 2026-10-05 23:46  左扬  阅读(6)  评论(0)    收藏  举报