缘起于我在审计源码时找到pvrdma的一个bug, 因此需要构建一个pvrdma设备来测试, 但是呢, 因为rdma硬件设备很贵, 所以测试只能用模拟的rdma设备, 这里面, 主要使用的是soft-RoCE类型的模拟设备.

本身这件事不算困难, 奈何网上的资料太少了, 导致我在这方面栽了不少跟头, 分享出来, 希望能够帮助到大家.

准备rdma相关驱动

最简单的方法, 使用centos7或者centos8, 执行lsmod |grep rdma, 如果存在rdma_rxe结果, 说明系统已经有了rdma相关驱动. 可以直接跳过这个准备步骤.

另一种方法, 编译一个新内核, 再编译用户态模块. 坑爹之路就此开始.

以下以ubuntu 16.04为例, 按照soft-RoCE的wiki指示开始编译内核.

  1. 下载此项目的v18分支. (按我的理解, 直接下载其它kernel源码来编译也是ok的, 没必要非得用这个, 我没有测试, 就不清楚行不行了)

  2. 准备相关编译组件

    1
    2
    3
    4
    5
    6
    sudo apt-get install git
    sudo apt-get install libncurses5-dev
    sudo apt-get install libssl-dev
    sudo apt-get install libibcm1 libibcm-dev ibverbs-utils libibverbs-dev
    sudo apt-get install libibverbs1 librdmacm-dev librdmacm1 rdmacm-utils
    sudo apt-get install libswitch-perl
  3. 进入项目, 开始配置编译选项

    1
    2
    cp /boot/config-`uname -r` .config
    make menuconfig

    弹出一个配置界面, 输入**/, 并键入RXE**, 会得到如下结果:

    image-20210525145220219

    按1, 跳转到改选项, 按M启用改模块, 之后回到这个配置的主界面保存配置, 再退出

    image-20210525145317380

  4. 开始编译并安装

    1
    2
    3
    4
    sudo make -j 4 # 此处的4是cpu的个数, 不要超过它, 不然编译可能更慢
    sudo make modules_install
    sudo make install
    sudo make headers_install INSTALL_HDR_PATH=/usr
  5. 重启并使用新编译的内核

  6. 下载用户态项目文件, 解压后进入目录

  7. 编译并配置

    1
    2
    3
    4
    5
    6
    7
    8
    ./configure --libdir=/usr/lib64/ --prefix=
    make
    make install

    sudo ln -s /usr/lib64/librxe.a /usr/lib/librxe.a
    sudo ln -s /usr/lib64/librxe.la /usr/lib/librxe.la
    sudo ln -s /usr/lib64/librxe-rdmav2.so /usr/lib/librxe-rdmav2.so
    sudo ln -s /usr/lib64/librxe.so /usr/lib/librxe.so
  8. 使用下列命令查看状态, 可能是如下效果

    1
    2
    3
    $ rxe_cfg status
    Name Link Driver Speed NMTU IPv4_addr RDEV RMTU
    ens160 yes vmxnet3 10GigE 1500 192.168.170.129
  9. 执行下列命令添加网卡并查看状态

    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
    $ rxe_cfg add ens160
    $ rxe_cfg start
    $ ibv_devices
    device node GUID
    ------ ----------------
    rxe0 020c29fffeee3b66
    $ ibv_devinfo
    hca_id: rxe0
    transport: InfiniBand (0)
    fw_ver: 0.0.0
    node_guid: 020c:29ff:feee:3b66
    sys_image_guid: 0000:0000:0000:0000
    vendor_id: 0x0000
    vendor_part_id: 0
    hw_ver: 0x0
    phys_port_cnt: 1
    port: 1
    state: PORT_ACTIVE (4)
    max_mtu: 4096 (5)
    active_mtu: 1024 (3)
    sm_lid: 0
    port_lid: 0
    port_lmc: 0x00
    link_layer: Ethernet

  10. 测试网络状态

    1
    2
    $ ibv_rc_pingpong -d rxe0 -g 1
    local address: LID 0x0000, QPN 0x000011, PSN 0x29338a, GID fe80::12cb:883b:5ccf:e656

    另外开启一个bash, 执行ibv_rc_pingpong -d rxe0 -g 1 127.0.0.1

    这时候server端会多出一行输出:

    1
    remote address: LID 0x0000, QPN 0x000012, PSN 0xe63ffc, GID fe80::12cb:883b:5ccf:e656

    client端会得到如下输出:

    1
    2
    local address:  LID 0x0000, QPN 0x000012, PSN 0xe63ffc, GID fe80::12cb:883b:5ccf:e656
    remote address: LID 0x0000, QPN 0x000011, PSN 0x29338a, GID fe80::12cb:883b:5ccf:e656

配置qemu编译选项

  1. 修改qemu项目中如下文件contrib/rdmacm-mux/meson.build

    1
    2
    3
    4
    5
    6
    7
    8
    9
      executable('rdmacm-mux', files('main.c'),
    dependencies: [glib, libumad],
    build_by_default: true,
    install: false)
    改为
    executable('rdmacm-mux', files('main.c'),
    dependencies: [glib, libumad],
    build_by_default: true,
    install: true)
  2. 执行如下编译配置

    1
    $ ./configure --enable-rdma --enable-pvrdma --enable-kvm  --enable-debug  --target-list=x86_64-softmmu
  3. 接下来就是make和make install了. 具体参考上一篇关于qemu编译的blog.

编译过程中可能遇到的问题

  1. 出现如下提示:

    1
    2
    3
    4
    5
    " OpenFabrics librdmacm/libibverbs/libibumad not present." \
    " Your options:" \
    " (1) Fast: Install infiniband packages (devel) from your distro." \
    " (2) Cleanest: Install libraries from www.openfabrics.org" \
    " (3) Also: Install softiwarp if you don't have RDMA hardware"

    查看项目build/config.log文件, 查看出错原因. 我的主要错误是:

    1
    if has error:config-temp/qemu-conf.c:1:27: fatal error: rdma/rdma_cma.h: No such file or directory

    通过执行find / -name "rdma_cma.h对整个硬盘搜索rdma_cma.h文件, 发现没找到, 就去网上找了它所在的librdmacm库, 下载解压后, 使用./autogen.sh && ./configure && make && make install安装即可.

  2. ERROR: Could not detect Ninja v1.7 or newer

    这个应该是本机安装的ninja组件, 或者版本过低, 可以通过如下命令下载安装解决

    1
    2
    3
    4
    5
    6
    $ wget https://github.com/ninja-build/ninja/releases/download/v1.8.2/ninja-linux.zip
    $ sudo unzip ninja-linux.zip -d /usr/local/bin/
    $ sudo update-alternatives --install /usr/bin/ninja ninja /usr/local/bin/ninja 1 --force
    输出:update-alternatives: using /usr/local/bin/ninja to provide /usr/bin/ninja (ninja) in auto mode
    $ /usr/bin/ninja --version
    1.8.2
  3. ../hw/block/virtio-blk.c:30:22: fatal error: scsi/sg.h: No such file or directory compilation terminated.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    $ wget http://launchpadlibrarian.net/353523714/libc6-dev_2.23-0ubuntu10_amd64.deb
    $ dpkg -i libc6-dev_2.23-0ubuntu10_amd64.deb
    (Reading database ... 184747 files and directories currently installed.)
    Preparing to unpack libc6-dev_2.23-0ubuntu10_amd64.deb ...
    Unpacking libc6-dev:amd64 (2.23-0ubuntu10) over (2.23-0ubuntu3) ...
    dpkg: dependency problems prevent configuration of libc6-dev:amd64:
    libc6-dev:amd64 depends on libc6 (= 2.23-0ubuntu10); however:
    Version of libc6:amd64 on system is 2.23-0ubuntu3.
    libc6-dev:amd64 depends on libc-dev-bin (= 2.23-0ubuntu10); however:
    Version of libc-dev-bin on system is 2.23-0ubuntu3.

    dpkg: error processing package libc6-dev:amd64 (--install):
    dependency problems - leaving unconfigured
    Errors were encountered while processing:
    libc6-dev:amd64
    $ apt -f install

启动参数

  1. 参照官方指导文档, 需要保证ib_cm模块没有加载, 因此先卸载相关模块. 并且加载必要模块

    1
    2
    $ rmmod rdma_ucm rdma_cm ib_cm
    $ insmod ib_umad

    注意, 默认情况下, rdmacm-mux文件的输出是调用的syslog, 建议改成printf 并把umad_open_port的返回结果输出出来, 以便找到失败原因.

  2. 在执行上述操作前, 请确保已经执行过了rxe_cfg start操作.

  3. 执行下列命令, 创建所需socket

    1
    2
    3
    4
    5
    $ qemu-6.0.0/build/contrib/rdmacm-mux/rdmacm-mux -d rxe0
    unix_socket_path=/var/run/rdmacm-mux-rxe0-1 这行是改成printf后会输出的内容.
    rdma-device-name=rxe0 这行是改成printf后会输出的内容.
    rdma-device-port=1 这行是改成printf后会输出的内容.
    Service started
  4. 创建桥接网络

    1
    2
    3
    4
    5
    6
    7
    $ apt-get install uml-utilities
    $ tunctl -t tap0 -u `whoami`
    $ chmod 0666 /dev/net/tun 让所有用户可读
    $ ifconfig tap0 192.168.2.1 up 给tap0设置ip段
    添加防火墙规则
    $ echo 1 > /proc/sys/net/ipv4/ip_forward
    $ iptables -t nat -A POSTROUTING -j MASQUERADE

    参考网址

  5. 最后, qemu启动参数

    1
    2
    3
    4
    5
    $ /usr/local/bin/qemu-system-x86_64 -object memory-backend-ram,id=mb1,size=1G,share=on -numa node,memdev=mb1 \
    -netdev tap,id=mynet0,ifname=tap0,script=no,downscript=no \
    -device vmxnet3,netdev=mynet0,id=net0,mac=52:54:00:e3:00:81,addr=0x10.0,multifunction=on \
    -chardev socket,path=/var/run/rdmacm-mux-rxe0-1,id=mads -device pvrdma,addr=0x10.1,ibdev=rxe0,netdev=net0,mad-chardev=mads \
    -m 1G -hda qemu-6.0.0/centos7.img --enable-kvm

    根据官方文档描述, pvrdma必须要三个参数: ibdev, netdev, mad-chardev, 并且To support it, pvrdma device is composed of two PCI functions, an Ethernet device of type vmxnet3 on PCI slot 0 and a PVRDMA device on PCI slot 1., 即在slot 0必须是一个vmxnet3网卡, slot 1是pvrdma, 所以此处vmxnet3的pci地址给的是 0x10.0, 则pvrdma是0x10.1.

libvirt配置

如果不想直接用命令启动qemu, 用libvirt也可

  1. 假如已经存在一个格式为qcow2的虚拟机image文件了, 假设文件名为centos7.img

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    安装相关组件
    $ apt install libvirt libvirt-python libguestfs-tools virt-install

    启动相关服务
    $ systemctl enable libvirtd
    $ systemctl start libvirtd

    查看桥接网卡
    $ brctl show
    bridge name bridge id STP enabled interfaces
    virbr0 8000.525400455887 yes virbr0-nic

    添加现成虚拟机
    $ virt-install --import --name vm1 \
    --memory 1024 --vcpus 1 --cpu host \
    --disk centos7.img,format=qcow2,bus=virtio \
    --network bridge=virbr0,model=virtio \
    --os-type=linux \
    --os-variant=centos7.0 \
    --nographics \
    --noautoconsole
  2. 安装后, 先关闭虚拟机, 然后在/etc/libvirt/qemu/目录下找到vm1.xml文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <domain type='kvm'>
    <name>vm2</name>
    <devices>
    <interface type='bridge'>
    <mac address='56:b4:44:e9:62:dc'/>
    <source bridge='virbr0'/>
    <model type='vmxnet3'/>
    <address type='pci' domain='0x0000' bus='0x00' slot='0x10' function='0x0' multifunction='on'/>
    </interface>
    </devices>

    <qemu:commandline>
    <qemu:arg value='-object'/>
    <qemu:arg value='memory-backend-ram,id=mb1,size=1G,share'/>
    <qemu:arg value='-numa'/>
    <qemu:arg value='node,memdev=mb1'/>
    <qemu:arg value='-chardev'/>
    <qemu:arg value='socket,path=/var/run/rdmacm-mux-rxe0-1,id=mads'/>
    <qemu:arg value='-device'/>
    <qemu:arg value='pvrdma,addr=10.1,ibdev=rxe0,netdev=bridge0,mad-chardev=mads'/> 注意这里是10.1 必须和vmxnet3的bus='0x00' slot='0x10' function='0x0' 对应, 保证bus相同, slot相同, 其中function: 0为vmxnet3, 1为pvrdma.
    </qemu:commandline>
    </domain>

    此处的bridge0是哪一个我不是很确定, 需要的人得自己琢磨一下了. 我配置这里主要是为了获取vmxnet3的真实启动配置参数.

常见的virsh命令:

  • 列出所有虚拟机
    virsh list --all
  • 获取虚拟机信息
    virsh dominfo vm1
  • 关闭虚拟机
    virsh shutdown vm1
  • 启动虚拟机
    virsh start vm1
  • 虚拟机随宿主机启动而自动启动
    virsh autostart vm1
  • 安全重启虚拟机
    virsh reboot vm1
  • 重启虚拟机(不安全,hard reset)
    virsh reset vm1
  • 删除虚拟机
    virsh shutdown vm1
    virsh undefine vm1
    virsh pool-destroy vm1

结语

一开始并不知道centos可以省去那么多麻烦, 在ubuntu上卡了很久很久, 问了Li Qiang大佬和官方的人, 也没有得到具体的配置方法, 最后总算是靠着一行行看配置文件找到失败原因, 并一一解决, 这里更不提那个softiWarp了, 这里虽然没用到, 但是那个错误提示里提到后, 我就以为需要配置它, 也踩了不少的坑 T-T.

在rdmacm-mux的编译和启动那一步也卡了很久, 不明白为什么启动不了server, 后来明白是自己给的设备名字不对, 并不是一个任意名字, 必须是设备名.

启动参数那也费了很多力气, 官方文档是用libvirt, 由于缺乏相关使用经验, 就先自己琢磨的参数, 后来实在有问题, 就用libvirt创建了虚拟机, 并修改了相关文件的配置, 添加成功命令, 并一步步找失败原因.

他们文档说是要让vmxnet3在slot 0, 但是并没有说怎么放到slot 0, 中间还遇到PCI: single function device can't be populated in function 10.1错误, 后来发现是vmxnet3缺少multifunction参数所致.

qemu官方文档真的太坑了, 信息给的少, 网上也缺乏相关资料, 只能自己琢磨, 唉, 坑啊~

最后呢, 是我发现的3个相关bug: cve-2021-3582, cve-2021-3607, cve-2021-3608, 虽然其中两个都有可能导致虚拟化逃逸, 但是现在没有任何厂商在用这个设备, 所以也就没什么实质性的危害.