R86S 万兆版提供了一个万兆网卡 CX341,而这款网卡有一个 SR-IOV 功能,这个功能可以实现(建议)硬件交换机,可以用来替换虚拟机和宿主机之间的软网桥,能大幅提升性能。

SR-IOV 是什么

SR-IOV 是 Single Root I/O Virtualization 的缩写,是网卡的一种虚拟化支持技术。它可以每让一个物理网口(PF)虚拟出多个网口(VF),这些 VF 都能于对应访问对应 PF 连接的设备。VF 之间, VF 和 PF 之间也能互相通讯。也就是说这时候就相当于虚拟了一个交换机。这些 VF 能直通给虚拟机,然后虚拟机之间能通过这个虚拟交换机通讯。由于这个虚拟交换机是内置在网卡硬件上的,所以它对比软网桥性能和带宽更好(注意,PF 本身是有速率设置的,比如可以设置 10G 或者 1G 等,但是这个并不影响网桥的转发速率,它始终都是以最高速率转发,即 10G 速率)。

启用 CX341 的 SR-IOV 功能

首先需要先安装网卡管理工具。

apt update && apt install mstflint

然后找到网卡设备 ID

lspci

找到 Ethernet controller: Mellanox Technologies MT27500 Family [ConnectX-3] 那一行,前面那串东西就是设备 ID 了,我这里是 05:00.0,后面的命令自行根据自己替换这串东西。
然后,需要保证网卡的固件版本足够高。

mstflint -d 05:00.00 query

现在的最新版是 2.42.5000。如果太低,可以去 https://network.nvidia.com/support/firmware/connectx3en/ 下载最新固件。XCGN 或者 XCCN 自己根据需求选择。
下载之后,使用命令

mstflint -d 05:00.00 -i 固件路径 b

刷入固件并重启。
保证固件最新之后,通过

mstconfig -d 05:00.00 set SR_IOV=1 NUM_OF_VFS=8

其中 8 是 VF 的数量,这里可以按照自己的需求修改。
固件开启之后,还需要在驱动开启 SR-IOV。新建文件 /etc/modprobe.d/mlx4_core.conf,内容如下:

options mlx4_core port_type_array=2,2 num_vfs=8 probe_vf=8

这里说明一下参数的意思。

  1. port_type_array=2,2 这个不要改。意思是两个网口的类型分别是 2 和 2。2 即以太网口。
  2. num_vfs 标识 VF 数量,格式为 a,b,c。其中 a 表示第一个网口的 vf 数量,b 表示第二个网口的 vf 数量,c 表示两个网口的 vf 数量。特别说明一下,这里两个网口 vf 数量不是说 a+b=c。这个配置生效之后,系统会创建 a+b+c 个虚拟 PCI 设备。创建的前 a 个设备直通其中一个到虚拟机后,里面会显示一个网口,这个网口能和第一个 pf 及其 vf 通讯。接着的 b 个设备同理,但是是第二个 pf 的。而剩下的 c 个设备,直通其中一个到虚拟机后,虚拟机能看到两个网口,这两个网口分别对应到两个 pf 的。如果只有一个数字,则跟 0,0,c 一样。也就是说,num_vfs=8num_vfs=0,0,8 一样。
  3. probe_vf 格式和 num_vfs 一样,这个表示在宿主机 ip link 显示的网卡的数量。如果 vf 并不准备给宿主机使用(毕竟宿主机可以直接使用 pf)则可以写成 0。

创建完这个文件(以及后面每次更新这个文件)后,运行一下命令应用修改:

update-initramfs -u

配置 VF

创建 SR-IOV 之后,重启机子之后,运行

ip link show

之后,应该能看到有两个网口(就是两个 pf)下面多出了一串 vf。这里说明一下,无论怎么配置 num_vfs,ip link 都会显示这两个网口的数量一样,并且都是 a+b+c 这么多个。但是我们知道,第一个网口应该只有 a+c 个 vf,而第二个只有 b+c 个。这是因为其中的有些是占位符,并不是真正的 vf。所以,对第一个 pf 而言,0 到 a 和 a+b 到 a+b+c 的 vf 才能用,对第二个而言只有 a 到 a+b+c 的 vf 才能用。
另外,我们可以看到 vf 的 mac 地址都是 0。如果都是 0,即直通虚拟机之后,它会自动随机分配 mac 地址。如果设置了,那么这个 mac 地址可以进入到虚拟机里面。下面来说一下 vf 的配置。
实例配置命令如下:

ip link set enp5s0 vf 0 mac f4:52:14:d9:81:a0 spoofchk on state enable vlan 41

这里设置第一个网口 enp5s0 的第 0 个 vf 的 mac 地址为 f4:52:14:d9:81:a0,并且打开了 spoofchk,state 设置成 enable,vlan 是 41。

  1. mac 大家都懂这里就不赘述了,注意不要设置成组播的 mac 就行。
  2. spoofchk 是指是否开启 mac 地址欺骗检查,默认关闭。如果打开之后,这个 vf 只能只能发出源 mac 地址为设置值的包。如果关掉,那这个 vf 什么源 mac 地址的包都能发出。这里建议关闭,没必要打开。
  3. state 表示状态,可以设置三种:auto, enable, disable。默认是 auto,表示只有 pf 启用(也就是说物理网口真的连上了模块)时候,对应的 vf 才能用。enable 表示无论 pf 是否启用(其实啥都没插)vf 也是启用状态(表现为看起来始终都插着模块)。disable 表示禁用这个 vf (始终显示没有插入模块)。
    这里补充一点,笔者测试时候,发现 enable 之后,即使链接信息显示正常,接口也成功启用,但是通讯会出现错误,具体在 dmesg 会显示 ``。应该是网卡本身的缺陷,无法解决。所以只能在物理口上始终插入一个设备了。
  4. vlan,大家都懂。要注意的是,这个 vlan 是硬件帮你 tag 的。也就是说 untag 的数据包进入 vf 会自动给 tag 上然后传到其他 vf 或者 pf,而这个 tag 的数据包进来会自动 untag。vlan 似乎是可以设置多个值,但是配置比较麻烦这里建议用多个 vf 来实现。既然是硬件 tag,那这个 tag 效率肯定是比软 vlan 要高的,这个是可以用来实现 PPPoE 拨号等的。

这里根据自己的实际拓扑进行配置。可以把配置命令放到 /etc/network/if-pre-up.d/vf 文件里,这会在网络接口(所有)启动时候自动运行。记得 chmod +x /etc/network/if-pre-up.d/vf

直通 VF

把 VF 弄出来之后就需要直通给虚拟机了。直通很简单,只需要在内核参数里面打开就可以了。编辑 /etc/default/grub,把里面的 GRUB_CMDLINE_LINUX_DEFAULT="quiet" 替换成 GRUB_CMDLINE_LINUX_DEFAULT="quiet iommu=pt intel_iommu=on" 即可。其实就是加入iommu=ptintel_iommu=on(用空格分开,需要放在引号内)。
然后应用修改:

update-grub

重启之后,在 PVE 里面就可以添加网卡直通了。刚才记下的网卡设备 ID 会出现多个,它们点后面的数字不同,比如我这里会有 05:00.01 这样的网卡,他就是第一个 vf 啦。对应前面 ip link 的 vf 顺序我们可以知道它对应几个和哪个物理网口了。

VF 放虚拟网桥

一般来说,我们创建 vf 之后,两个网口一个是 wan 一个是 lan 了。lan 口出来接个光电交换机就能联通更多设备。然而,r86s 上面会还有三个 2.5G 的口,这些口有时候也是作为 lan 使用的,这时候需要把光口和电口桥接起来了。直通之后,就是说我们要把 vf 和另外两个电口软桥接起来。而 r86s 里面同样,我们一般还会弄一个电口的管理口,管理口需要同时能访问 r86s 和光口的设备,这时候也需要桥接。但是如果我们直接把 vf 放在虚拟网桥里面(比如在 Openwrt 上,把直通后的 vf 加入 br-lan 中)之后,pf 和其他 vf 并不能访问 br-lan 的 IP。而且还有一个神奇的现象,就是 pf 和其他 vf 能通过 DHCP 获取 IP。

这是为什么呢?

这时候需要我们回顾小学学过的二层交换机的原理。跟集线器不一样,二层交换机一个很重要的特性就是 mac 地址学习。就是说,mac 地址会通过 ARP 或者收到的包来学习哪些 mac 地址对应到哪个网口,然后能把数据包发到那个网口上,这时候其他网口就收不到不属于自己的包,减少了网络负担,也减少了干扰(就是不用 CSMA/CD 了,网线可以更长,带宽可以更高)。
但是,这个网卡 SR-IOV 的 PF 和 VF 之间并不是一个真正的二层交换机,但也不是一个集线器,而是一个半残废的交换机。具体来说,它没有 mac 地址学习功能,它的 mac 地址表是静态的,这个表就是记录我们给 vf 设置的 mac 地址(ip link 设置的)。那么,在关闭 spoofchk 的时候,我们能发送以另一个 mac 地址为源的包出去,但是,由于没有 mac 地址学习功能,当收到这个 mac 地址为目标的包的时候,网卡内部交换机并不知道应该发到哪个 vf 上,而是直接发到 物理口(注意这里物理口和 PF 不一样,物理口上的物理设备能收到包,但是系统内 PF 接口也是收不到的)上,所以这时候 vf 就收不到响应包了。而由于网桥一般有自己的 mac,所以这时候网卡并不知道这个 mac 的目的地是这个 vf,所以就收不到响应包,所以就不能通讯啦。由于 DHCP 请求是广播包,所有 vf 都能收到,br-lan 自然可以响应,包也能正常发出,所以客户端能获取到 IP 地址。但是真正访问 br-lan 的时候,过了一次 ARP (ARP 也是广播)之后,就有明确 mac 地址了,而这时候网卡不会转发这个到 br-lan 的 vf 上,自然也就不能通讯了。

那么如何解决这个问题呢,最简单的办法就是把 br-lan 的 mac 设置成 vf 设置的 mac,这时候,vf 就能接收到目标是 br-lan 的包了。

但是,
要注意的是,这样设置之后,只能解决 vf 下联设备访问 br-lan 的问题,并不能解决 br-lan 其他下联设备访问 vf 下联设备。因为 vf 下联设备访问 br-lan 下联设备时候,mac 不是 br-lan 本身,所以还是会转到 pf 上。

那么怎么解决这个问题呢?我们最多只能给 vf 设置一个 mac 地址,不可能把所有下联设备的 mac 都设置成 vf 的地址,也不能给 vf 设置多个 mac 地址。

然而,
vf 确实能设置多个 mac 地址。以下命令

bridge fdb add f4:52:14:d9:81:a0 self permanent dev enp5s0v0

就能让 enp5s0v0 这个 vf 接受目标 mac 地址为 f4:52:14:d9:81:a0 的数据包了。
也就是说,即使我不把 br-lan 的 mac 地址设置成 vf 的地址,我只需要用上面命令把 br-lan 的 mac 地址添加到桥接到 br-lan 的 vf 上,就能让 vf 也接受目标是 br-lan 的包,然后 br-lan 就能正确接收到包了。

另外,官网上的文档说 vf/pf 能设置混杂模式,设置方法为

ifconfig enp5s0v0 promisc

这样就能给 enp5s0v0 这个 pf/vf 开启混杂模式。开启混杂模式之后,无论目标 mac 地址是否匹配这个 pf/vf,都会把包转发过来。然而实测并没用,应该和 state enable 一样是网卡本身的缺陷,这里不讨论这个。

上面能让 vf 接受网桥自身发出的包还没完,网桥还会连接其他设备,以其他设备的包的 mac 为目标的怎么办呢?我们没办法枚举所有 mac 地址并且一个一个加入进去。这时候,我们要监听网桥加入的设备,然后当有新设备接入网桥(被网桥的 mac 学习功能学习到了)的话,就添加进去。当被移除的话,就添加进去。监听我们可以利用 bridge monitor 功能来监听,每当加入或者删除 mac 地址时候,都会输出一行日志,我们解析日志,把 mac 和网桥名字提取出来,然后扫描网桥下的设备,根据驱动判断是否是 vf/pf,然后用命令添加网桥。我写了一个脚本:

#/bin/bash
reg='(([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2})[ ]+[^ ]+[ ]+[^ ]+[ ]+master[ ]+([^ ]+)'

for if in $(ls /sys/class/net); do
    if ! [ -e /sys/class/net/${if}/device/driver ]; then continue; fi
    driver=$(basename $(readlink /sys/class/net/${if}/device/driver))
    if [ "$driver" = "mlx4_core" ] && [ -d /sys/class/net/${if}/brport ]; then
        mac=$(cat /sys/class/net/${if}/brport/bridge/address)
        echo "bridge fdb add ${mac} permanent self dev ${if}"
        bridge fdb add ${mac} permanent self dev ${if}
    fi
done

mac_add() {
    mac=$1
    br=$2
    echo "mac ${mac} added to br ${br}"
    ifs=($(ls /sys/class/net/${br}/brif))
    for if in ${ifs[@]}; do
        driver=$(basename $(readlink /sys/class/net/${if}/device/driver))
        if [ "$driver" = "mlx4_core" ]; then
            echo "bridge fdb add ${mac} permanent self dev ${if}"
            bridge fdb add ${mac} permanent self dev ${if}
        fi
    done
}

mac_del() {
    mac=$1
    br=$2
    echo "mac ${mac} deleted from br ${br}"
    ifs=($(ls /sys/class/net/${br}/brif))
    for if in ${ifs[@]}; do
        driver=$(basename $(readlink /sys/class/net/${if}/device/driver))
        if [ "$driver" = "mlx4_core" ]; then
            echo "bridge fdb delete ${mac} permanent self dev ${if}"
            bridge fdb delete ${mac} permanent self dev ${if}
        fi
    done
}

bridge monitor | while read -r line;
do
    arr1=($(echo "$line" | sed -nr "s/^$reg$/\1 \3/p"))
    arr2=($(echo "$line" | sed -nr "s/^Deleted $reg$/\1 \3/p"))
    if [ ${#arr1[@]} -eq 2 ]; then
        mac_add ${arr1[0]} ${arr1[1]}
    elif [ ${#arr2[@]} -eq 2 ]; then
        mac_del ${arr2[0]} ${arr2[1]}
    fi
done

就是完成这个工作的。另外还有 C++ 写的版本,并且我预先编译了一个静态版本,可以放 PVE 或者 openwrt 上都是能直接运行的。直接设置开机自动运行这个程序就能非常自动添加 mac 到 VF/PF 上了。

当然,我这里是在 PVE 上测试的,因为我希望 PVE 上的 Openwrt 死了我也能访问 PVE。这时候我需要把电口桥接到 PVE 的 vmbr 上,所以我的桥接其实是在 PVE 上完成的(vmbr 桥接了所有电口以及两个物理口的其中一个 VF)。因此这个脚本是在 PVE 上跑的。如果有时间,我会写一个 C 语言版本的,到时候就不用这么麻烦的解析了。不过也是有时间再说啦。

硬 VLAN 拨号

TODO

20G 端口聚合

TODO

已有 19 条评论


  1. w先生

    博主的这篇文章写的真的很棒,把一个复杂问题描述的浅显易懂。
    宿主系统:unraid
    网卡型号:hp 544+flr,40g双光口网卡
    虚拟机软路由:openwrt
    我是开启物理网口1的sr-iov之后,一个sriov虚拟网卡直通给openwrt接入br-lan。物理网口1留在unraid系统中,同时物理网口1还通过dac线连接一台个人电脑。
    按照博主的办法,我只能在宿主机上使用bridge fdb add命令才行(因为我并没有把物理口直通给openwrt,但是bridge fdb add命令是要作用于pf的),那么宿主机怎么知道openwrt的br-lan网桥学习到的MAC表呢(在我的网络环境下)?

    w先生  March 6th, 2023 at 03:41 pm回复
    1. w先生

      或者说,bridge fdb add是可以作用于pf也可以作用与vf,“In the SR-IOV environment, the Ethernet driver can share the existing 128 MACs (for each port) among the Virtual interfaces (VF) and Physical interfaces (PF) that share the same table as follow:”这是官方文档的原文,物理口和寄生在该物理口上的虚拟口其实是共享这个mac表的。那这样的话,在openwrt上也可以对直通过去的VF使用bridge fdb add命令,那这样的话就可以使用openwrt的br-lan网桥学习到的MAC表了。

      请问博主,我理解的两种情况,哪一种是对的呢?

      w先生  March 6th, 2023 at 03:47 pm回复
  2. w先生

    我昨天晚上试用了一下,VF直通给openwrt之后,在openwrt里可以对虚拟网卡是用bridge fdb add命令,例如VF在openwrt里是eth3,那么就对eth3使用命令就行了。非常感谢博主写的脚本,真的很棒。

    w先生  March 7th, 2023 at 09:17 am回复
  3. w先生

    博主你好,还有个问题也是比较棘手,不知道如何解决,请博主帮忙分析一下。我是开启物理网口1的sr-iov之后,一个sriov虚拟网卡直通给openwrt接入br-lan。物理网口1留在unraid系统中,同时物理网口1还通过连接一台个人电脑。但是物理网口连接的电脑不是24小时开机,当电脑关机时,开启sriov的物理网口不是“up”状态,貌似sriov的虚拟交换机就不工作了,那么就访问不到unraid了。请问有什么好的解决办法吗?

    w先生  March 23rd, 2023 at 10:26 am回复
    1. yujincheng08

      看我之前的博文,这些卡其实支持一种始终 up 的模式,也就是说即使物理口没有连设备他也能是起来的状态。但是,口子看起来是起来了,但是 vf 和 pf 之间还是不能通讯。这个问题无解。。

      yujincheng08 admin March 31st, 2023 at 05:44 pm回复
  4. CCrraawwlleerrggoo

    ${974423628+899096110}

    CCrraawwlleerrggoo  July 16th, 2023 at 07:10 am回复
  5. ccc

    你好,请问下vf网卡可以实现vlan透传吗?有无说明文档阿?

    ccc  July 22nd, 2023 at 02:19 pm回复
    1. yujincheng08

      能啊。官方文档有介绍。

      Note: In ConnectX-3 adapter cards family, switching to VGT mode can also be done by setting vlan_id to 4095.

      yujincheng08 admin September 7th, 2023 at 12:46 am回复
  6. tutugreen

    之前知道VF之间互联有亿点问题,博主的解决方案很有意思

    tutugreen  August 6th, 2023 at 10:06 pm回复
  7. dean.jiang

    遇到一个问题,pve下配置好i350 t4的sriov之后,把1口的其中一个vf与23口的pf直通给ikuai做lan,4口做wan,l口下的几个vf之间通信正常,1口插无线路由器,wifi client可以正常上网。但是1口下的设备不能与2口插的设备通信。在这里,我应该怎么做呢?
    bridge fdb add f4:52:14:d9:81:a0 self permanent dev enp5s0v0
    这条命令,mac地址是不是该填2口pf的mac地址,然后enp5s0v0改成直通给ikuai的vf网口号,并且这条命令要在直通给ikuai之前执行?毕竟ikuai启动之后enp5s0v0 vf网卡在pve下就没了。

    dean.jiang  August 14th, 2023 at 09:10 pm回复
  8. dean.jiang

    如果在2口3口都使用独立的lan dhcp倒是没问题,虽然不同网段但还是可以互联,就不会出现该问题了。
    但我现在需要123口不管pf还是vf连接的设备都在一个网段,有点难搞。

    dean.jiang  August 14th, 2023 at 09:12 pm回复
  9. dean.jiang

    想了一下,我这个和你遇到的场景不太一样,我这是pf1下的vf与pf2无法通信,好像不太适用你的方案,不知道楼主有没有什么办法。

    dean.jiang  August 14th, 2023 at 11:32 pm回复
    1. yujincheng08

      把 pf1 的 vf 和 pf2 的 vf 用网桥桥起来。

      yujincheng08 admin September 7th, 2023 at 12:43 am回复
      1. gayliuzi

        你好,你说的把 pf1 的 vf 和 pf2 的 vf 用网桥桥起来,是指在pve环境下桥起来吗?
        我自己是pve环境,我在pfsense这个vm里将直通给pfsense的pf1 的 vf 和 pf2 的 vf用透明桥接在一起,发现无法联通上游,pfsense的黑白界面里有时提示找不到arp地址,桥本身分配的静态ip也联不通,无法进入web管理,感觉和你说的情况有点像,求解惑

        gayliuzi  October 9th, 2023 at 06:25 am回复
  10. xu

    你好,我的i350-t2一个口开了sriov然后分配一个vf直通给ikuai做lan口使用,其他设备通信没问题,设置vlan时就不行,同样的vf在op上面可以正常识别vlan

    xu  September 1st, 2023 at 10:53 am回复
  11. Mildnes

    大佬,我想请教一个问题,我查了资料。CX3有几种sriov的分配方式,我现在是这样设置的

    Mildnes  October 16th, 2023 at 08:15 am回复
    1. Mildnes

      options mlx4_core port_type_array=2,2 num_vfs=15,0,0 probe_vf=15,0,0
      这个我看说明是给第一个端口开启sriov,但是我这样设置了,第二个端口用vm无法跟上游路由通信,好像也无法跟这个sriov通信,现在就是不知道该怎么弄,我是单独的一个vm,已经安装了编译好的静态版本。
      我的连线是一张CX3PRO直通,然后绑定到了路由里面,用两根dac线分别连接另一张cx3的网卡(sriov+vm),我想这样使用。。就是遇到了问题。想请教下。

      Mildnes  October 16th, 2023 at 08:18 am回复
      1. Mildnes

        再补充一下,我开启sriov的网卡的PF绑定到vm,sriov就会失效,然后断网。。另一个口PF绑定到VM,就直接断网

        Mildnes  October 16th, 2023 at 08:52 am回复
  12. 我是阿牛呀

    博主,您好,我看了你的文章需要再咨询一下,怎么透传交换机vlan,就是变tunrk口。这样我可以用交换机拨号。请邮件联系我,有偿解决。

    我是阿牛呀  November 4th, 2023 at 11:17 pm回复

发表新评论

© 2024 Powered by Typecho & Theme Quark
粤ICP备17055048号