实验报告
一、实验设备与环境
- 操作系统:Ubuntu 24.04 (真实的物理机环境,非虚拟机)
- 网络硬件:物理无线网卡
wlp0s20f3(IP: 192.168.51.106)
二、实验目标
- 在 Ubuntu 物理机上通过 SoftRoCE 搭建虚拟 RDMA 实验环境,配置虚拟 RTE 网卡。
- 基于
libibverbs库编写 C 语言程序,尝试完成一次单边通信的 RDMA-WRITE 操作。 - 使用 Wireshark 抓取 RoCEv2 数据包,并对以太网/UDP/BTH/RETH 头部协议进行简单分析。
三、实验过程
1、环境配置与虚拟网卡挂载:
首先在系统安装了构建环境和 RDMA 工具链。通过 sudo modprobe rdma_rxe 加载内核模块后,使用指令 sudo rdma link add rxe0 type rxe netdev wlp0s20f3 成功将虚拟 RDMA 设备 rxe0 绑定到了物理机的 WiFi 网卡上。通过 ibv_devices 命令验证了设备已成功激活。
sudo apt install -y build-essential cmake libudev-dev libnl-3-dev libnl-route-3-dev \ ninja-build pkg-config valgrind python3-dev cython3 python3-setuptools \ rdma-core perftest ibverbs-utils rdmacm-utils wireshark
2、编写 RDMA-WRITE 测试代码:
编写了 rdma_simple_write.c。代码逻辑中包含了打开设备、分配保护域 (PD)、注册内存区域 (MR) 并赋予读写权限、创建完成队列 (CQ) 和队列对 (QP)。
这里需要记录的ID是1,使用编号为1的wifi无线网络绑定,代码编写如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <infiniband/verbs.h>
int main() {
struct ibv_device **dev_list = ibv_get_device_list(NULL);
struct ibv_context *ctx = ibv_open_device(dev_list[0]);
struct ibv_pd *pd = ibv_alloc_pd(ctx);
char *src = aligned_alloc(4096, 4096);
char *dst = aligned_alloc(4096, 4096);
strcpy(src, "SUCCESS: RDMA Write Packet Captured!");
struct ibv_mr *mr_src = ibv_reg_mr(pd, src, 4096, IBV_ACCESS_LOCAL_WRITE);
struct ibv_mr *mr_dst = ibv_reg_mr(pd, dst, 4096, IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE);
struct ibv_cq *cq = ibv_create_cq(ctx, 10, NULL, NULL, 0);
struct ibv_qp_init_attr init_attr = {
.send_cq = cq, .recv_cq = cq,
.cap = { .max_send_wr = 10, .max_send_sge = 1 },
.qp_type = IBV_QPT_RC
};
struct ibv_qp *qp = ibv_create_qp(pd, &init_attr);
// --- 状态转换 ---
struct ibv_qp_attr attr = { .qp_state = IBV_QPS_INIT, .port_num = 1, .qp_access_flags = IBV_ACCESS_REMOTE_WRITE };
ibv_modify_qp(qp, &attr, IBV_QP_STATE | IBV_QP_PKEY_INDEX | IBV_QP_PORT | IBV_QP_ACCESS_FLAGS);
attr.qp_state = IBV_QPS_RTR;
attr.path_mtu = IBV_MTU_1024;
attr.dest_qp_num = qp->qp_num;
attr.rq_psn = 0;
attr.max_dest_rd_atomic = 1;
attr.min_rnr_timer = 12;
attr.ah_attr.is_global = 1;
attr.ah_attr.port_num = 1;
// 【重要:修改此处的 Index】
// ibv_devinfo -v 中看到的 RoCE v2 索引
ibv_query_gid(ctx, 1, 1, &attr.ah_attr.grh.dgid);
ibv_modify_qp(qp, &attr, IBV_QP_STATE | IBV_QP_AV | IBV_QP_PATH_MTU | IBV_QP_DEST_QPN | IBV_QP_RQ_PSN | IBV_QP_MAX_DEST_RD_ATOMIC | IBV_QP_MIN_RNR_TIMER);
attr.qp_state = IBV_QPS_RTS;
attr.sq_psn = 0;
attr.timeout = 14;
attr.retry_cnt = 7;
attr.rnr_retry = 7;
attr.max_rd_atomic = 1;
ibv_modify_qp(qp, &attr, IBV_QP_STATE | IBV_QP_TIMEOUT | IBV_QP_RETRY_CNT | IBV_QP_RNR_RETRY | IBV_QP_SQ_PSN | IBV_QP_MAX_QP_RD_ATOMIC);
// --- 循环发送,确保 Wireshark 能抓到 ---
struct ibv_sge sge = { .addr = (uintptr_t)src, .length = 64, .lkey = mr_src->lkey };
struct ibv_send_wr wr = {
.wr_id = 1, .opcode = IBV_WR_RDMA_WRITE, .send_flags = IBV_SEND_SIGNALED,
.sg_list = &sge, .num_sge = 1,
.wr.rdma.remote_addr = (uintptr_t)dst, .wr.rdma.rkey = mr_dst->rkey
};
struct ibv_send_wr *bad_wr;
printf("Starting 10 RDMA Writes. Check Wireshark now!\n");
for(int i=0; i<10; i++) {
ibv_post_send(qp, &wr, &bad_wr);
struct ibv_wc wc;
while(ibv_poll_cq(cq, 1, &wc) < 1);
printf("Packet %d sent.\n", i+1);
sleep(1);
}
return 0;
}
编译运行:
gcc rdma_simple_write.c -libverbs -o rdma_simple_write
- 使用 perftest 验证拓扑:
为了验证底层 RDMA 链路是否连通,开启了两个终端,使用官方工具ib_write_bw进行了本地回环测试:- 服务端:
ib_write_bw -d rxe0 -x 1 - 客户端:
ib_write_bw -d rxe0 -x 1 127.0.0.1
测试结果显示成功打通,并跑出了约 7.28 Gb/sec 的平均带宽,证明底层的 SoftRoCE 链路是完全正常工作的。
- 服务端:
- 抓包与头部协议分析(理论验证阶段):
针对 RoCEv2 协议进行了分析。明确了 RoCEv2 是封装在 UDP 之上的,标准目的端口为 4791。其核心数据包结构包含:- UDP 头部:标识 4791 端口。
- BTH (基础传输头):包含操作码(Opcode = 0x0a 代表 RDMA Write Only)、目标 QP 号和 PSN(包序号)。
- RETH (扩展传输头):包含 Virtual Address(目标内存地址)、Remote Key(内存访问权限密钥)以及 DMA Length。
四、实验遇到的问题及尝试解决的过程
在整个实验过程中,遇到了几个非常经典的底层网络与驱动机制问题,排查过程如下:
问题一:C 程序编译时找不到头文件
- 现象:执行
gcc rdma_simple_write.c -libverbs时报错fatal error: infiniband/verbs.h: No such file or directory。 - 解决过程:发现系统只安装了运行时环境,缺少开发用的头文件。通过执行
sudo apt install libibverbs-dev安装了对应的开发包后,编译顺利通过。
问题二:运行自己编写的 C 代码后,不仅没有效果,Wireshark 也抓不到任何包
- 现象:代码能正常打印出分配的 Local Addr 和 RKey,程序瞬间执行完毕,但在网卡上没有任何流量产生。
- 解决过程:经过分析 RDMA 的状态机机制发现,RDMA 编程与传统 Socket 不同,创建 QP 后它默认处于
RESET状态。我的代码仅仅将 QP 转换到了INIT状态。而要让网卡真正发包,必须实现复杂的握手逻辑(交换远端的 QPN、IP、RKey 和虚拟地址),并依次将 QP 状态切换至RTR (Ready to Receive)和RTS (Ready to Send)。因为缺少了这部分状态机切换代码,网卡处于静默状态,所以没有任何物理包发出。
问题三:使用官方 ib_write_bw 跑出 7Gbps 带宽,但 Wireshark 依然抓不到 UDP 4791 端口的包
- 现象:在 Wireshark 中无论监听
wlp0s20f3、lo还是any接口,使用过滤器udp.port == 4791始终抓不到数据包。 - 解决过程与排查步骤:
- 排查 GID 索引:起初怀疑流量走的是 RoCEv1(无 UDP 封装)。在 Ubuntu 24.04 下没有
show_gids命令,于是通过ibv_devinfo -v rxe0 | grep -A 4 "GID\["查看网卡属性,明确查到了GID[ 1]对应的是当前 WiFi 的 IP 且标注了RoCE v2。因此在运行ib_write_bw时加上了-x 1参数强制走 RoCEv2。 - 排查 Wireshark 显示问题:发现 Wireshark 默认列表不显示端口号且不自动解析 4791 端口。尝试了通过“Decode As”强制将 4791 端口解析为 RoCEv2 协议。
- 发现底层闭环机制:在确认上述配置全部正确,且客户端明确输出 7.28 Gb/sec 带宽的情况下,抓包工具依然没有任何输出。最终确认这是由于 Linux 内核与 SoftRoCE 驱动的底层优化所致。在物理机上进行同一网卡内的单机回环测试时,SoftRoCE 的数据流直接在内核驱动的底层缓存中完成了闭环,直接绕过了标准 Linux 网络协议栈(sk_buff)。而 Wireshark 和 tcpdump 依赖于挂载在网络层接口上的 pcap 抓包机制,因此这种被底层驱动短路的流量彻底处于“隐形”状态,导致无法通过常规手段在物理机的单机回环中抓到包。
- 排查 GID 索引:起初怀疑流量走的是 RoCEv1(无 UDP 封装)。在 Ubuntu 24.04 下没有
尝试了三种方法,均找不到端口4791的包,实验至此无法继续深入。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 kipleyarch@gmail.com