概述

源进源出,也叫记录上一跳,即请求报文从某条路径进入,回应报文依然沿着同样的路径返回,而不是通过查找路由表来确定接口,保证了报文从一个接口进出。

在我们正常的网络环境下,如果要保证业务正常,就是对整网的路由进行规划,确保回应报文能够回到请求发送者,这也是源进源出的效果。但是有两个场景通过路由规划来实现源进源出不现实:Linux作为互联网接入设备、应用负载均衡记录源。下面以互联网接入设备为场景介绍Linux的源进源出,应用负载均衡记录源将在后面的文章中介绍。

URPF

在介绍场景之前,先介绍一下URPF。

URPF(Unicast Reverse Path Forwarding)单播逆向路径转发,网络设备收到一个报文时首先获取包的源地址和入接口,然后以源地址为目的地址,在路由转发表中查找相对应的转发接口是否与入接口匹配,如果不匹配就认为这个源IP是伪装的,并将该报文丢失。

URPF检查分严格型和松散型,严格型要求报文的入接口与报文源IP路由的出接口一致,松散型仅要求路由器的路由表中存在报文源IP的路由即可。

严格型URPF

如下图,Router上有四个接口,且仅有四条路由表,当Eth0收到报文时,首先以源IP为目的地址查找路由,第一个报文的源IP和入接口与路由表匹配,接收进入一步转发;第二个和第三个报文的源IP和入接口与路由表不匹配,将报文丢弃:

Linux源进源出_urpf

松散型URPF

如下图,Router上有四个接口,且仅有四条路由表,当Eth0收到报文时,首先以源IP为目的地址查找路由,第一个报文的源IP和入接口与路由表匹配,接收进入一步转发;第二个报文的源IP和入接口与路由表不匹配,但是路由表存在到源IP的路由,所以接收进入一步转发;第三个报文的源IP不存在于路由表中,将报文行弃:

Linux源进源出_meta mark_02

互联网接入设备

如下图,Linux作为互联网接入设备,并连接了两个联通和电信两个运营商,每个链路都配置了公网IP,并在Linux上配置DNAT,使用户可以从互联网上访问内网的服务器。

Linux源进源出_urpf_03

当一个用户通过互联网访问内网服务器时,从外到内的请求报文肯定可以到达内网服务器,但是服务器的回应报文往外转发到Linux时,将通过哪条链路转发出去,就不太好判断。可以在Linux上面导入到联通和电信的路由,通过查找路由来确定回包链路。但是对于移动,以及其它运营商的路由就有问题。首先,虽然运营商的地址大体上确定,但是也会有小范围的变化,在Linux上无法做到实时更新路由;其次,对于移动或者其它运营商的用户访问内网,从哪条链路进入Linux不能确定,这要根据各运营商之间路由通行来确定。如果一个用户的请求报文通过联通链路进入,但是回报文通过电信转发出去,就可能导致非常慢,甚至丢包的情况。

针对这个场景,就要通过源进源出来实现,针对每一个请求报文,Linux记录好报文从哪个接口进行,对应的回应报文就从哪个接口转发出去。

源进源出实现

回顾前斯的策略路由实验,每个报文在查找路由表的时候,都可以通过匹配不同的条件来确定使用哪个路由表,以现实指定从哪个接口转发出去的目的,等于针对每一个回应报文,都要判断他对应的请求报文是从哪个接口进来的。回顾IP报文的格式,在报文中并没有哪个字段携带对应请求报文的信息,更不会记录是从哪个接口接收的,所以想通过查找报文中的信息以实现指定出接口无法实现。

Linux在转发报文的时候,除了查路由表,查每个链每个表的策略,也会对请求和回应报文构成的会话进行记录,如下图所示,Linux记录了请求和回应两个方向报文的五元组:

Linux源进源出_ct mark_04

针对有DNAT和SNAT的场景,对比请求和回应报文的五元组,就可以确定是否做了NAT。

在Linux系统中,在转发第一个请求报文时,就是会产生一条会话,后续的请求和回应报文都会匹配之前产生的会话。同时在会话中有一个mark标记,叫做ct mark,这个属性只记录在会话表中,而meta mark是在报文转发过程中携带,通过ct mark和meta mark的相互转换,实现源进源出的需求。

结合Linux的转发流程图来分析源进源出的实现方式:

Linux源进源出_urpf_05

请求报文

当请求报文进入prerouting链时,先匹配从哪个接口进入,为不同的接口添加不同的ct mark:

//创建链,以根据不同的入接口设置不同的ct mark
nft add chain ip tb_0 meta_mark '{ type filter hook prerouting priority -150; }'
//针对eth0接口接收到请求方向的报文设置ct mark为0x1000
nft add rule ip tb_0 meta_mark iif eth0 ct direction original ct mark set 0x1000
//针对eth1接口接收到请求方向的报文设置ct mark为0x1001
nft add rule ip tb_0 meta_mark iif eth1 ct direction original ct mark set 0x1001
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

针对请求方向的报文设置完ct mark后,将进入其它流程(如dnat, routing等),这些流程正常处理,请求报文最终会转发到内网服务器。通过设置ct mark后,系统内的会话表就是记录mark值:

Linux源进源出_记录上一跳_06

如上图所示,会话表中的mak被设置成了0x1000(十进制为4096)和0x1001,ct mark的设置,不会影响任何转发机制,只是一个标记。

回应报文

针对回应报文进入prerouting链时,要根据会话中的ct mark设置meta mark。默认情况下,全部会话的ct mark都为0,所以只要处理非0的ct mark即可:

//针对回应报文,只要ct mark不为0,说明要从请求报文的入接口往外转发,不再简单的查找路由表
//把ct mark复制到meta mark
nft add rule ip tb_0 meta_mark ct direction reply ct mark != 0x00000000 meta mark set ct mark
  • 1.
  • 2.
  • 3.

报文出prerouting链后进入Routing模块,Routing模块首先查找ip rule规则,根据不同的meta mark使用不同的路由表,以实现从不同的接口往外转发:

//如果meta mark为0x1000,说明请求报文从eth0进入,就查找路由table_eth0,以实现把回应报文从eth0转发出去:
ip rule add fwmark 0x1000 table table_eth0 priority 3
//如果meta mark为0x1001,说明请求报文从eth1进入,就查找路由table_eth1,以实现把回应报文从eth0转发出去:
ip rule add fwmark 0x1001 table table_eth1 priority 4
  • 1.
  • 2.
  • 3.
  • 4.

修改rt_tables 文件,在里面添加路由表table_eth0和table_eth1,每个路由表要有一个唯一的ID,修改之后的文件如下:

Linux源进源出_meta mark_07

报文在转发过程中通过ip rule规则进入路由表查找路由,所以我们需要在对应路由表中添加对应的路由:

//进入路由表table_eth0的报文全部转发到联通
ip route add default via 113.0.0.2 table table_eth0
//进入路由表table_eth1的报文全部转发到电信
ip route add default via 58.0.0.2 table table_eth1
  • 1.
  • 2.
  • 3.
  • 4.

关闭URPF

Linux默认是开启URPF,因为在连接运营商的链路上并没有配置路由,即可以导致从运营商链路进来的流量会全部被丢弃,所以要关闭接口的urpf检查,关闭脚本如下:

for i in /proc/sys/net/ipv4/conf/*/rp_filter
do 
{
echo 0 > $i
}
done
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

至此,Linux作为互联网接入设备,实现源进源出的功能已全部现实。

总结

Linux的源进源出,主要有几个动作:

1。在请求报文的入接口针对会话设置ct mark;

2。在回应报文的入接口根据会话的ct mark设备报文的meta mark;

3。根据报文的meta mark指定不同的路由表;

4。由对应路由表中的缺省路由指定出接口为请求报文的入接口。