rmda学习1:深入理解GID

GID(Global Identifier)是 RDMA(特别是 RoCEv2)网络中用于唯一标识一个 RDMA 端点的 128 位标识符,其核心作用相当于 TCP/IP 协议栈中的 IP 地址。理解 GID 的生成、查询与使用,是调试 RDMA 连接问题的关键。


0.1. 1. GID 的生成与作用

0.1.1. 1.1 GID 从哪里来?

  • RoCEv1:GID 基于网卡端口的 MAC 地址 生成,只能在二层广播域内通信。
  • RoCEv2(当前主流):GID 由网卡端口绑定的 IP 地址 动态生成。每个 IPv4 或 IPv6 地址都会在 GID 表中产生一个条目。

关键点:RoCEv2 数据包外层使用 UDP/IP 封装,IP 头部的源/目的 IP 地址正是从通信双方的 GID 转换而来。因此,GID 直接决定了 RDMA 报文在网络中的路由路径。

0.1.2. 1.2 GID 与 ip addr 的关系

对于一个网络接口(如 eth1),其配置的 IP 地址会直接影响 RDMA 设备端口的 GID 表。例如:

1
2
3
4
5
# 查看 eth1 的 IP 地址
$ ip addr show eth1
eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP
inet 29.127.153.4/27 brd 29.127.153.31 scope global eth1
inet6 fe80::5200:e6ff:fed9:4bbb/64 scope link

这两个 IP 地址(一个 IPv4,一个 IPv6 链路本地)会在对应的 RDMA 设备(如 mlx5_1)的 GID 表中生成多个条目。

0.1.3. 1.3 show_gids 命令详解

show_gids 是查看 GID 表最直接的命令(通常由 infiniband-diags 包提供)。在您的环境中,针对 mlx5_1 设备的输出为:

1
2
3
4
5
6

$ show_gids | grep mlx5_1
mlx5_1 1 0 fe80:0000:0000:0000:5200:e6ff:fed9:4bbb v1 eth1
mlx5_1 1 1 fe80:0000:0000:0000:5200:e6ff:fed9:4bbb v2 eth1
mlx5_1 1 2 0000:0000:0000:0000:0000:ffff:1d7f:9904 29.127.153.4 v1 eth1
mlx5_1 1 3 0000:0000:0000:0000:0000:ffff:1d7f:9904 29.127.153.4 v2 eth1

列含义

  • DEV:RDMA 设备名称(mlx5_1)。

  • PORT:设备的物理端口号(通常为 1)。

  • INDEX:GID 在表中的索引号(从 0 开始)。这是编程时最重要的字段

  • GID:128 位的十六进制标识符。对于 IPv4 映射的 GID,格式为 ::ffff:<IPv4>

  • IPv4:如果该 GID 是由 IPv4 地址生成的,此处显示 IPv4 地址;若为 IPv6 链路本地地址,则显示 -

  • VER:版本。v1 对应 RoCEv1(基于 MAC),v2 对应 RoCEv2(基于 IP)。现代数据中心普遍使用 v2

  • NETDEV:关联的 Linux 网络接口名(eth1)。

解读

  • 索引 0:IPv6 链路本地地址,RoCEv1。

  • 索引 1:IPv6 链路本地地址,RoCEv2。

  • 索引 2:IPv4 映射地址 ::ffff:29.127.153.4,RoCEv1。

  • 索引 3:IPv4 映射地址,RoCEv2。

您的测试表明,索引 0 和 1 可用于 QP 通信,而索引 2 和 3 在您的驱动/固件环境下不可用(导致 ibv_modify_qp 到 RTR 失败)。这很可能是由于特定版本的 mlx5 驱动对 IPv4 映射 RoCEv2 GID 的支持不完整。


0.2. 2. ibv_query_gid() 函数解析

在 RDMA 编程中,ibv_query_gid() 用于从指定设备的端口读取特定索引的 GID。

0.2.1. 2.1 函数原型

1
2
int ibv_query_gid(struct ibv_context *context, uint8_t port_num,
int index, union ibv_gid *gid);
  • context:通过 ibv_open_device() 打开的 RDMA 设备上下文。

  • port_num:设备的物理端口号(通常为 1,但若设备有多个端口则可选)。

  • index:GID 表的索引(0, 1, 2…)。这是开发者必须明确选择的值

  • gid:输出参数,返回查询到的 16 字节 GID(定义为 union ibv_gid { uint8_t raw[16]; })。

0.2.2. 2.2 示例代码中的硬编码问题

常见示例代码中往往这样写:

1
2
3
4
5
union ibv_gid my_gid;
if (ibv_query_gid(ctx, 1, 0, &my_gid)) {
fprintf(stderr, "ibv_query_gid failed\n");
exit(1);
}

这里硬编码了 port_num = 1index = 0这在生产环境中是不安全的,原因如下:

  • RoCEv1 vs v2:索引 0 通常是 RoCEv1 的 GID(基于 MAC),而多数现代网络要求使用 RoCEv2(基于 IP)。若强制使用索引 0,可能无法利用 RoCEv2 特性。

  • IPv4 vs IPv6:如果网络通信基于 IPv4,您应该选择 IPv4 映射的 GID 索引(如 3),而不是链路本地 IPv6 GID。但在您的环境中,索引 3 不可用,因此需要回退到索引 1(IPv6 RoCEv2)。

  • 多网口/容器环境:GID 表的有效索引可能不是 0、1,而是更大的数字(如 4、5)。硬编码索引会导致查询到全零的无效 GID。

0.2.3. 2.3 正确的动态选择 GID 索引的方法

生产级代码应该遍历 GID 表,根据条件选择合适的索引。典型的策略:

  1. 跳过全零 GID(无效条目)。

  2. 优先选择 RoCEv2 类型(通过读取 /sys/class/infiniband/.../gid_attrs/types/<idx> 判断)。

  3. 在已知 IPv4 映射 GID 不可用时,自动回退到 IPv6 RoCEv2 GID

  4. 可选:匹配关联的 netdev 名称(通过 sysfs)。

以下是一个针对您环境的动态选择函数示例(优先使用 RoCEv2,跳过 IPv4 映射的索引 2/3,选择索引 1):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
int select_working_gid_index(struct ibv_context *ctx, uint8_t port_num) {
char path[256];
for (int idx = 0; idx < 256; ++idx) {
union ibv_gid gid;
if (ibv_query_gid(ctx, port_num, idx, &gid) != 0)
break;
// 检查 GID 是否全零
int all_zero = 1;
for (int i = 0; i < 16; ++i)
if (gid.raw[i] != 0) { all_zero = 0; break; }
if (all_zero)
continue;
// 读取 GID 类型
snprintf(path, sizeof(path),
"/sys/class/infiniband/%s/ports/%d/gid_attrs/types/%d",
ctx->device->name, port_num, idx);
FILE *f = fopen(path, "r");
if (f) {
char type[32];
fgets(type, sizeof(type), f);
fclose(f);
if (strstr(type, "RoCE v2")) {
// 跳过 IPv4 映射的 GID(根据您环境实测不可用)
// 判断前12字节为0且第13-16字节为 0xffff 开头的 IPv4 映射格式
if (gid.global.subnet_prefix == 0 &&
(gid.global.interface_id & 0xffffffff00000000) == 0xffff00000000) {
printf("Skipping IPv4-mapped GID index %d (unusable)\n", idx);
continue;
}
printf("Selected usable RoCE v2 GID index %d\n", idx);
return idx;
}
}
}
// 回退:直接返回索引 1(已知可用)
fprintf(stderr, "No dynamic GID found, fallback to index 1\n");
return 1;
}

然后在 main() 中使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int gid_idx = select_working_gid_index(ctx, 1);
if (gid_idx < 0) {
fprintf(stderr, "No valid GID index\n");
goto cleanup;
}
if (ibv_query_gid(ctx, 1, gid_idx, &my_gid)) {
fprintf(stderr, "ibv_query_gid failed\n");
goto cleanup;
}
printf("Local GID (index %d): ", gid_idx);
for (int i = 0; i < 16; i++) printf("%02x", my_gid.raw[i]);
printf("\n");
// 在 QP 的 RTR 阶段设置
attr.ah_attr.grh.sgid_index = gid_idx;

0.3. 3. GID 在 QP 状态迁移中的作用

在将 QP 从 INIT 切换到 RTR(Ready to Receive)时,必须提供对端的 GID 和本端的 GID 索引:

1
2
3
4
attr.ah_attr.is_global = 1;
attr.ah_attr.grh.dgid = *(union ibv_gid*)remote_gid; // 从 TCP 交换获得的对端 GID
attr.ah_attr.grh.sgid_index = local_gid_index; // 本端选中的索引
attr.ah_attr.port_num = 1;
  • dgid:目标 GID,将填入 RoCEv2 报文的 目的 IP 地址

  • sgid_index:本地 GID 索引,指示硬件使用哪个源 IP 地址发送数据包。

如果 sgid_index 指向的 GID 对应的 IP 地址无法路由到对端(例如,选择了 IPv4 映射但该索引在驱动中不可用),RoCEv2 数据包将无法送达,导致 ibv_modify_qp 返回错误(如您遇到的 Failed to modify QP to RTR)。


0.4. 4. 针对您环境的总结与建议

GID 索引

类型

可用性

说明

0

IPv6 链路本地 RoCEv1

可用

旧版本,不建议

1

IPv6 链路本地 RoCEv2

可用

推荐使用,稳定可靠

2

IPv4 映射 RoCEv1

不可用

驱动/固件不支持

3

IPv4 映射 RoCEv2

不可用

驱动/固件不支持(导致 RTR 失败)

结论

  • 您的 RDMA 环境健康,只是 IPv4 映射 RoCEv2 GID 未被驱动正确支持。

  • 在生产代码中禁止硬编码 GID 索引,必须实现动态选择逻辑,优先选择可用的 RoCEv2 索引(在您环境中为索引 1)。

  • 使用 show_gidsibv_query_gid 配合 sysfs 读取类型,可以写出可移植的代码。

  • 如果您需要抓包,由于 IPv6 GID 产生的 RoCEv2 报文使用 IPv6 头部,tcpdump 过滤条件应为 ip6 and udp port 4791 或直接 udp port 4791


0.5. 5. 附录:常见调试命令

1
2
3
4
5
6
7
8
9
10
# 查看 GID 表
show_gids
# 查看 GID 类型(通过 sysfs)
cat /sys/class/infiniband/mlx5_1/ports/1/gid_attrs/types/*
# 查看 RDMA 设备与 netdev 映射
ibdev2netdev
# 抓取 RoCEv2 包(需硬件镜像激活)
tcpdump -i mlx5_1 -n udp port 4791
# 使用 perftest 自带抓包(推荐替代方案)
ib_write_bw -d mlx5_1 -i 1 --report_gbits -k 1

通过以上内容,您应该能够理解 GID 的本质、查询方法、编程注意事项,并针对您的环境做出正确的选择。


rmda学习1:深入理解GID
https://martinbj2008.github.io/2026/05/17/2026-05-17-gid-in-rdma.ai/
Author
Martinbj2008
Posted on
May 17, 2026
Licensed under