多网卡环境下利用策略路由实现网络流量同进同出

当一台机器有超过1块网卡,并且配置的IP地址不在一个段里的时候,会出现在外部只有一个IP地址能够ping通的情况,举个最简单的例子:

一个有2块网卡的机器:

[root@test]# ip addr
2: enp24s0f0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 11:11:11:11:11:11 brd ff:ff:ff:ff:ff:ff
    inet 192.168.100.10/24 brd 192.168.100.255 scope global enp24s0f1
       valid_lft forever preferred_lft forever
3: enp24s0f1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 22:22:22:22:22:22 brd ff:ff:ff:ff:ff:ff
    inet 192.168.200.10/24 brd 192.168.200.255 scope global enp24s0f1
       valid_lft forever preferred_lft forever

可以看到两块网卡,分别配置了192.168.100.10192.168.200.10这两个IP,再看看机器的默认路由:

[root@test]# ip route
default via 192.168.100.1 dev enp24s0f0
192.168.100.0/24 dev enp24s0f0 proto kernel scope link src 192.168.100.10
192.168.200.0/24 dev enp24s0f1 proto kernel scope link src 192.168.200.10

从路由可以发现这机器的默认网关是192.168.100.1,那么,如果跨越网关去ping这台机器的话,会出现只有192.168.100.10能通的情况:

[root@test2]# ping 192.168.100.10 -c 2
PING 192.168.100.10 (192.168.100.10) 56(84) bytes of data.
64 bytes from 192.168.100.10: icmp_seq=1 ttl=56 time=1.84 ms
64 bytes from 192.168.100.10: icmp_seq=2 ttl=56 time=0.804 ms

--- 192.168.100.10 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 0.804/1.319/1.835/0.515 ms

[root@test2]# ping 192.168.200.10 -c 2
PING ping 192.168.200.10 (ping 192.168.200.10) 56(84) bytes of data.
^C
--- ping 192.168.200.10 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1062ms

可以看到只有第一个IP能通。那么,如果想让两个IP都能通信需要怎么做呢?我们简单分析一下:

假设ping的源主机IP地址是192.168.1.20,那么ping包会首先到达192.168.200.0/24的网关,在这个场景是192.168.200.1,然后网关把包转发给机器的第二块网卡,从接收的这条链路来说,是没有问题的。

那么回包的链路呢?因为发起通信的源IP不在同网段,所以会查询路由表,会根据路由表发给默认网关,而测试机器的默认网关是192.168.100.1,需要将流量从第一块网卡出去。

从回包的链路考虑就会有一些问题,相当于流量从一块网卡进来,但是又从另外网卡出去。默认情况下,Linux会通过一个Reverse Path Filtering (rp_filter)的机制,限制这种情况发生。
那么要修改也很简单:

[root@test2]# echo "net.ipv4.conf.all.rp_filter = 0" >> /etc/sysctl.conf
[root@test2]# sysctl -p

配置rp_filter=0之后,再去测试,发现第二块网卡已经可以正常被外部访问了。

到这里,网络通断问题算是解决了,但是呢,流量路径并不是最优解。目前这个情况,所有出去的流量全都走网卡1,一方面网卡2的出方向带宽浪费了,另外一方面如果网卡1出现一些问题导致中断,那网卡2的IP地址也无法访问了,可用性并没有提升。

针对两块网卡的机器,一个很自然的想法,是不是可以某一块网卡进来的流量,也从同一块网卡出去呢?

这里就需要策略路由的帮助了,其实这个问题,可以转化成根据source地址选择不同的路由,因为从这台机器发出去的网络包,源地址不是192.168.100.10,就是192.168.200.10,那需求就很简单了,如果发现源地址是192.168.100.10,那就走网卡1的默认路由出去,如果源地址是192.168.200.10,那就走网卡2的默认路由出去。这样的话,相当于机器上有两个默认路由了,这样可以么?

答案是可以的,不过需要将这两个默认路由分到两张不同的路由表上。根据这篇Routing Tables文章可以看到Linux支持很多张路由表,其实默认我们用ip route命令操作的就是main这张表。通过加表id或者名字的方式,可以让ip命令操作不同的路由表。

[root@test]# ip route add default via 192.168.200.1 dev enp24s0f1 table 101
[root@test]# ip route show table 101
default via 192.168.200.1 dev enp24s0f1

这里添加了一条新的默认路由default via 192.168.200.1 dev enp24s0f1到编号为101的表里。现在有了路由表,还需要一个策略,来让对应源地址的流量查找这张路由表就行了:

[root@test]# ip rule add from 192.168.200.0/24 table 101
[root@test]# ip rule show
0:      from all lookup local
32765:  from 192.168.200.0/24 lookup 101
32766:  from all lookup main
32767:  from all lookup default

这里加了一条规则,如果源地址在192.168.200.0/24内,就使用101这张路由表,而这张路由表就上面加的以网卡2为出口的。这样就实现了网卡流量同进同出的需求。

其实可以通过ip rule show看到,默认有几条规则,优先级最高的规则是local这张表,其次是刚添加的规则,然后是main这张表,最后是default这张表。

最后还有一个问题,这些配置怎么持久化呢?参考红帽的文档Networking Guide,可以在/etc/sysconfig/network-scripts目录下,添加rule-XXXXroute-XXXX文件,实现配置持久化:

[root@test]# cat route-enp24s0f1
default via 192.168.200.1 dev eth1 table 101
[root@test]# cat rule-enp24s0f1
from 192.168.200.0/24 table 101

添加了配置文件后,执行ifup enp24s0f1就可以实现自动的配置了。