通过 ip route table 与 ip rule 命令分离路由表,解决同一 Linux 主机同网段双网卡路由冲突只能用一个的问题。
背景
实验室的服务器集群,每台机器安装有 2 个 InfiniBand(简称 IB)网卡,支持 RDMA 也兼容 TCP/IP 。
实验室的 IB 网卡 IP 地址 (IPoIB) 约定格式为:172.16.[主机编号].[网卡序号]/16
,其中 网卡序号
为 1 或 2 ,对应网络接口 0 或 1 ,每台机器的双网卡以及不同机器的 IB 网卡均位于同一网段。
实验室通常每人使用两台机器,一台作为 cpu server 另一台作为 memory server 通过 IB 及 RDMA 进行 Far Memory 相关实验。下表为本人使用的机器配置:
主机名称 | 操作系统版本 | IB 网卡接口名称 | IPoIB |
cpuserver16 | Ubuntu 20.04.5 LTS With Desktop | ibs5f0 | 172.16.16.1/16 |
ibs5f1 | 172.16.16.2/16 | ||
memserver34 | Ubuntu 18.04.3 LTS No Desktop | ib0 | 172.16.34.1/16 |
ib1 | 172.16.34.2/16 |
其他同学相应对照自己机器的配置,可使用 ip address
或 ifconfig
命令查看自己机器上的 IB 网卡接口名称和 IP 地址。如果找不到 IB 网卡则需要安装 MLNX_OFED 驱动。
RDMA 连通性测试
在 RDMA 协议栈下使用 rping
工具测试网络连通性,该工具等价于 TCP/IP 的 ping
。与 ping
不同,rping
工作时需首先启动一个 server side 进程,然后从 client side 试图向 server side 发起连接(而 ping
不需要 server side )。
RDMA Server 端示例:
rping -s -a 172.16.34.1 -p 9401 -v
# -s: 启动 server 端进程
# -a: 绑定 IP 地址 (某个 IPoIB,这里选取 memserver34 第一个 IB 网卡)
# -p: 监听端口号 (缺省为 9400)
# -v: 打印输出信息
# server side 启动后首先阻塞等待 client 连接
# 当有 client 完成连接并断开后 server side 才停止
RDMA Client 端示例:
rping -c -I 172.16.16.1 -a 172.16.34.1 -p 9401 -v
# -c: 启动 client 端进程
# -I: 指定自身使用的 IPoIB 地址 (可缺省,如缺省则从路由表查找)
# -a: 服务端的 IPoIB 地址
# -p: 服务端监听的端口号
# -v: 打印输出信息
# client side 连接成功后将不停发送测试数据,按 Ctrl+C 停止
# 可选 -C 选项限定发送测试数据次数,例如 -C 10
如果 RDMA 连接正常,server 和 client 的终端均会有数据输出,例如:
ping data: rdma-ping-0: ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqr
ping data: rdma-ping-1: BCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrs
问题描述与复现
在 Memory Server 上使用第二块 IB 网卡作为 RDMA Server 时,位于 CPU Server 的 RDMA Client 无法与 RDMA Server 通讯,发生 RDMA_CM_EVENT_REJECTED
错误。示例命令:
# memserver34
rping -s -a 172.16.34.2 -p 9401 -v
# cpuserver16
rping -c -a 172.16.34.2 -p 9401 -v
而后 client side 发生错误:
cma event RDMA_CM_EVENT_REJECTED, error 8
路由表排查
使用 route -n
命令或 ip route
命令查看 memserver34 的路由表,结果如下:
$ ip route
default via 10.208.130.254 dev enp49s0f1 proto static
10.208.130.0/24 dev enp49s0f1 proto kernel scope link src 10.208.130.34
172.16.0.0/16 dev ib0 proto kernel scope link src 172.16.34.1
172.16.0.0/16 dev ib1 proto kernel scope link src 172.16.34.2
系统自动生成的路由表同时存在两条指向 172.16.0.0/16
的路由,但只有第一条被优先匹配,所以如果用第二块 IB 网卡作为 server 则无法成功连接。
分离路由规则
由于所有机器的 IB 网卡均应处于同一子网,默认的路由规则一定出现冲突,为了使两块网卡能同时使用,应让每块 IB 网卡分别通过各自的路由表,而不使用全局的路由表。
参考 这篇解答 给出路由表修改命令(以 memserver34 为例):
# 以下命令必须以 root 用户或通过 sudo 执行!
# 删除全局路由表中与 ib 网卡有关的所有条目,确保不重复
ip route del 172.16.0.0/16 dev ib0
ip route del 172.16.0.0/16 dev ib1
# 指定 table 选项,加回 ib 网卡路由,table 编号为数字且双 ib 网卡之间不相同
# 表明该条路由信息属于指定的路由表
ip route add 172.16.0.0/16 dev ib0 proto kernel scope link src 172.16.34.1 table 941
ip route add 172.16.0.0/16 dev ib1 proto kernel scope link src 172.16.34.2 table 942
# 指定路由规则,两块 ib 网卡分别通过各自的路由表
ip rule add from 172.16.34.1 table 941
ip rule add from 172.16.34.2 table 942
cpuserver 的配置类似,替换下 IPoIB 地址即可。配置完成后的参考路由表如下:
$ ip route
default via 10.208.130.254 dev enp49s0f1 proto static
10.208.130.0/24 dev enp49s0f1 proto kernel scope link src 10.208.130.34
$ ip rule
0: from all lookup local
32764: from 172.16.34.2 lookup 942
32765: from 172.16.34.1 lookup 941
32766: from all lookup main
32767: from all lookup default
$ ip route show table 941
172.16.0.0/16 dev ib0 proto kernel scope link src 172.16.34.1
$ ip route show table 942
172.16.0.0/16 dev ib1 proto kernel scope link src 172.16.34.2
此时 IB 网卡的路由信息已分到两张路由表 (ip route table) 并通过路由规则 (ip rule) 分别绑定了。
需要注意的是,当 IB 网卡按上述方法分离了路由规则(尤其是删除了全局路由)后,使用 rping
和 ping
时必须显式指定本地使用的网络接口( -I
选项):
# 16 ping 34
ping -I ibs5f0 172.16.34.1
ping -I ibs5f0 172.16.34.2
ping -I ibs5f1 172.16.34.1
ping -I ibs5f1 172.16.34.2
# 16 rping 34(ib0)
# runs on 34
rping -s -a 172.16.34.1 -p 9401 -v
# runs on 16
rping -c -I 172.16.16.1 -a 172.16.34.1 -p 9401 -v
rping -c -I 172.16.16.2 -a 172.16.34.1 -p 9401 -v
# 16 rping 34(ib1)
# runs on 34
rping -s -a 172.16.34.2 -p 9401 -v
# runs on 16
rping -c -I 172.16.16.1 -a 172.16.34.2 -p 9401 -v
rping -c -I 172.16.16.2 -a 172.16.34.2 -p 9401 -v
目前通过 ip route
命令对路由表的修改并未永久保存,重启后失效。可将上述命令存为 shell 脚本,重启系统首次登录后执行。
$ vim ~/route-ib-mem34.sh
#!/bin/bash
set -e
if [ $(whoami) != "root" ]; then
echo "Error: Must run as root!"
exit 1
fi
echo "delete global ib routes"
ip route del 172.16.0.0/16 dev ib0
ip route del 172.16.0.0/16 dev ib1
echo "add ib routes with table"
ip route add 172.16.0.0/16 dev ib0 proto kernel scope link src 172.16.34.1 table 941
ip route add 172.16.0.0/16 dev ib1 proto kernel scope link src 172.16.34.2 table 942
echo "add ip rule for ib interface"
ip rule add from 172.16.34.1 table 941
ip rule add from 172.16.34.2 table 942
永久保存配置
为了无需每次开机都手动执行上述脚本,需要将上述路由修改脚本部署为开机自动执行。更准确地来说,在 IB interface 建立连接 (up) 后执行。
不同版本的 Ubuntu 系统,甚至不同的具体机器,使用的网络管理软件可能是不同的,在配置前需要先弄清楚当前机器采用何种工具管理网络,否则容易配错。
NetworkManager / ifupdown
cpuserver16 这台机器的系统有桌面,所以网络由 NetworkManager 管理,相应的终端工具有 nmcli
和 nmtui
,IB 网卡的静态 IP 配置位于 /etc/NetworkManager/system-connections/
。
在 /etc/network/if-up.d
目录下添加名为 route-ib-cpu16
的脚本。if-up.d
目录下的脚本会在网络接口发生连接后自动执行,通过 IFACE
变量来传入当前的网络接口名称。
脚本内容(需要通过 IFACE
变量判断一下当前的网络接口):
#!/bin/bash
set -e
if [ "$IFACE" == "ibs5f0" ]; then
ip route del 172.16.0.0/16 dev ibs5f0
ip route add 172.16.0.0/16 dev ibs5f0 proto kernel scope link src 172.16.16.1 table 941
ip rule add from 172.16.16.1 table 941
elif [ "$IFACE" == "ibs5f1" ]; then
ip route del 172.16.0.0/16 dev ibs5f1
ip route add 172.16.0.0/16 dev ibs5f1 proto kernel scope link src 172.16.16.2 table 942
ip rule add from 172.16.16.2 table 942
fi
脚本文件创建后,必须添加可执行权限: sudo chmod +x route-ib-cpu16
,而后重启服务器即可令上述配置永久生效。
另外查阅了一些资料,如果当前 Linux 系统用 ifupdown
工具管理网络连接,上述方法可能也可以,但没试过。
netplan
与 cpuserver16 不同,memserver34 采用 netplan 管理网络连接。该台机器安装有 Ubuntu 18.04 LTS 系统且不含桌面,即没有 NetworkManager
。
经过一番尝试在 /etc/netplan/
目录找到了 IB 网卡的静态 IP 配置:
$ ls /etc/netplan/
01-netcfg.yaml 99-netcfg.yaml
$ cat /etc/netplan/99-netcfg.yaml
network:
version: 2
renderer: networkd
ethernets:
......
ib0:
addresses: [172.16.34.1/16]
ib1:
addresses: [172.16.34.2/16]
对于 netplan ,网卡连接后执行脚本位于 /etc/networkd-dispatcher/
中,具体地,有关路由的操作放在 routable.d
子目录下。脚本编写规范同上,netplan 通过传入 IFACE
变量指定当前网口。
在 /etc/networkd-dispatcher/routable.d/
目录下创建 route-ib-mem34
:
#!/bin/bash
set -e
if [ "$IFACE" == "ib0" ]; then
ip route del 172.16.0.0/16 dev ib0
ip route add 172.16.0.0/16 dev ib0 proto kernel scope link src 172.16.34.1 table 941
ip rule add from 172.16.34.1 table 941
elif [ "$IFACE" == "ib1" ]; then
ip route del 172.16.0.0/16 dev ib1
ip route add 172.16.0.0/16 dev ib1 proto kernel scope link src 172.16.34.2 table 942
ip rule add from 172.16.34.2 table 942
fi
同理需要 sudo chmod +x
添加可执行权限,重启后该路由配置永久生效。
如果不确定永久配置是否正确,在重启后可以用 分离路由规则 章节提到的 ip rule
和 ip route show table
命令测试一下。