eBay 流量管理之 DSR 在基础架构中的运用及优化

一、背景

在现代企业内部, 负载均衡器 (LoadBalancer,以下简称 LB ) 被大量使用。负载均衡器的常用模式如下图 1 所示:

图 1 负载均衡的常用模式(代理模式)

负载均衡的常用模式可概述为下(详情可见: 分享 | eBay 流量管理之负载均衡及应用交付 ):

  1. 客户端向负载均衡器提供的虚拟 IP 地址 VIP 发送请求 (CIP → VIP)
  2. 负载均衡器从提前配置的多个子网 IP 地址中选择一个 (SNAT IP),替代客户端请求的源 IP,并依据负载均衡算法,选择一个服务器 IP 作为目的 IP,发送请求 (SNIP → SIP)
  3. 服务器处理后将响应结果发回负载均衡器 (SIP → SNIP)
  4. 负载均衡器将最终结果返回给客户端 (VIP → CIP)

但负载均衡器的这种 经典 SNAT 模式 ,在基础架构运维中有以下 缺点

  1. 由于信息量的因素,网络请求的回包往往会比请求包大很多。一般达到 10 倍 [1], 20 Mbps 的请求,其回包可能达到 200 Mbps 。这样一来,由于回包也是经过 LB,就会大量增加 LB 的带宽使用,减小 LB 的有效处理容量。
  2. 基础架构的服务 (DNS,MAIL,LDAP 等)工作在 TCP/UDP 传输层之上,所以无法像其它工作在 HTTP 协议以上的应用那样,用 HTTP header 里边的 X-Forwarded-For 字段来保存客户端真实 IP。 这些基础架构服务的请求包在被 LB 进行 SNAT 之后,客户端的真正 IP 被替换为 LB 的 SNAT IP 。 这样造成的结果就是,后端服务器无法知道真实的客户端 IP 是什么,给问题排查、攻击检测、运行指标统计等运维活动带来极大不便。

而 DSR (Direct Server Return, 服务器直接返回) 技术(其在 Layer 2 实现时叫 Direct Routing,F5 称之为 nPath), 顾名思义,就是让后端服务器绕开 LB 直接回包给客户端 (如下图 2[2]),从而实现节省 LB 带宽和获取客户端真实 IP 的目标。

图 2 DSR 数据流图

二、原理

那么 DSR 又是通过怎样的独到之处,跟 LB 的看家本领 SNAT 叫板的呢? 原来,在 DSR 模式下,当网络请求包到达 LB 时,LB 并不做 SNAT,而是把包的源 IP 地址原封不动地转发给后端服务器 。 当后端服务器拿到请求包以后,由于包里边携带了客户端的源 IP 地址,它就可以直接将回包通过网络路由给这个源 IP 地址,到达客户端手中。

不过,等等,这幸福来得也太突然了吧?让我们来仔细分析一下,看看这中间的重重险阻在哪里。

设想一下,服务端如果傻傻地用 [src=SIP;dst=CIP] 回包,那么客户端收到这个包以后,会怎么样?——当然是丢掉。为什么?因为客户端也不傻,它知道自己请求的是 VIP,所以期待的是 VIP 给它的回包。至于这个服务端 SIP 又是个什么鬼?不需要,丢弃即可。因此,为了绕过客户端这道安全防线,聪明的工程师们想到了一个绝好的办法, 就是让服务端伪装自己的 IP 地址为 VIP,用 [src=VIP;dst=CIP] 回包,以期能骗过客户端,让客户端误以为是 LB 的 VIP 回复给它的

那是不是我们直接在服务端网卡上配置 VIP,让服务端通过该网卡应答就可以?答案是 No,因为这样虽然确保了客户端收到的应答包是 [src=VIP,dst=CIP],但带来的问题是:服务端和 LB 将同时应答 ARP,引发 IP 地址冲突。所以,服务端伪装 VIP 可以,但一定要低调,万万不可让其他人知道—— 有一个绝好的办法,就是将 VIP 偷偷地配置到 loopback 网卡或者 tunl0 这些本地的网络接口上 ,既骗过了内核,让它以为真的有这个合法 IP 地址,又不会被外边的设备发现,一石二鸟,想想都激动。

还有一个问题,怎么样才能让服务端内核以 [src=VIP,dst=CIP] 的组合回包呢?由于内核是严格按照 (src→dst, dst→src) 的方式进行回包的,那让服务端内核如此回包的最好办法,当然是让 LB 转发 [src=CIP,dst=VIP] 的包给服务端的内核啦。但是,这听上去有点像天方夜谭,服务端的 VIP 是一个“偷偷摸摸”的配置,所以 LB 发出的 [src=CIP,dst=VIP] 的 IP 包,在路由时,一定是找到了 LB 上的 VIP,绝无可能被路由到服务端去。怎么办?我可太“南”了…

针对上述问题,有一个办法,就是 LB 跳过 L3 三层路由,直接把 [src=CIP, dst=VIP] 的 IP 包,塞到一个目标 MAC 地址为服务端网卡 MAC 地址的 L2 网络包里,送达服务端网卡(相当于将从客户端到 LB 的 L2 数据包更换一下目标 MAC 地址。对应于 NAT,这种实现方法也被称作 MAT,即 MAC Address Translation[3],参见图 3)。

图 3 L2 DSR 替换请求包的目标 MAC 地址

这个方法实现简单,但有一个硬伤,就是当 LB 和后端服务器不在同一个 L2 网络的时候就无能为力了。所以我们还是回到 L3 网络来考虑这个问题,也就是如何让 LB 把 [src=CIP,dst=VIP] 的 IP 包转发给服务端的内核。 如果把网络通信类比于传统信件,那我们现在的需求就是,让 LB 送一封“收件人”为 VIP 的信件,并且保证该信件是送到服务端而不是 LB 自己 。 通过这一类比,我们不难想出一个好办法,就是让 LB 给服务端送一封信 ([dst=SIP]),然后在这封信里再嵌入一封“收件人”为 VIP 的信 ([dst=VIP]);同时在外层信封上标注——这里边装的不是 TCP,也不是 UDP,而是另外一封信(协议)。这样当服务端收到外边的信封以后,它继续拆开里面的信,就可以获取到 [src=CIP,dst=VIP] 的数据了。

事实上,这种做法就是 网络隧道 (Network Tunnel) 的概念——将一种网络协议包 (inner packet) 作为 payload 嵌入外层网络协议包中,并利用外层网络协议进行寻址和传输,从而实现原本割裂的内层网络像隧道一样被打通了的效果。

最常见的网络隧道就是我们使用的 VPN:当拨入 VPN 之后,我们就可以访问 10.x.x.x 这种内网地址了。很显然是 10.x.x.x 这种 IP 包被封装进了某种特殊的通道,也就是一些基于公网 VPN 协议的网络隧道。 类似于 VPN,在 DSR 这种场景下,我们就是要将访问 VIP 的 IP 包通过至 SIP 形成的隧道进行传输 。 Linux 支持的 IP in IP 协议主要有 IPIPGRE 两种,IPIP 隧道不对内层 IP 数据进行加密 [4],所以它最简单,效率最高,如图 4 所示。由于是内网可信网络,因此选用 IPIP 这种最简单高效的隧道协议。

图 4 IPIP 隧道协议

我们可以让 LB 生成这样的一个 IPIP 包 [src=CIP, dst=SIP, protocol=IPIP, payload=[src=CIP, dst=VIP] ] 并发出去。由于外层的 IP 包目标地址是服务端 IP,所以外层 IP 包顺利到达服务端内核;服务端内核拆包一看,这里边又是一个 IPIP 包,于是进一步解包,发现是 [src=CIP,dst=VIP] 的包。 这时内核检查 dst=VIP 是本地一个合法的 IP 地址(因为本地网卡已经配置了 VIP),于是欣然接受,并根据 TCP/UDP 的目标端口转给应用程序处理 。 而应用程序处理完,就顺理成章地交由内核,按照 (src→dst, dst→src) 的原则,产生 [src=VIP, dst=CIP] 的 IP 包进入网络路由至客户端,问题解决!此时的数据流图参见下图 5:

图 5 使用 IPIP 隧道的 DSR 数据流图

三、探究

下面,我们以业界普遍使用的 F5 负载均衡器 LTM + CentOS 7 为例,实战分析探究一下 DSR 配置的关键技术。

Step 1

如下所示,在 LB 上配置一个有 2 个成员的负载均衡池 (pool),并创建一个 VIP 对应到该 pool:

  • 该 pool 指定的 profile 为 IPIP,表示 LB 和该 pool 的成员通讯,走 IPIP 隧道;
  • 要关闭 PVA-Acceleration,因为 DSR 模式下,client 不再直接与 VIP 建立 TCP 连接,所以用不到 L4 的 PVA 硬件加速;
  • 该 VIP 指定 translate-address disabled,表示不对请求该 VIP 的流量进行 SNAT。

Step 2

此时,我们在 server 端先不做任何配置,直接从 client 去 telnet VIP,并且从 server 端分别用 VIP 和 CIP 抓包(如表 1),看有什么现象发生。

表 1 分别用 VIP 和 CIP 作为条件抓包

从 wireshark 中可以看到,LB 在收到 [CIP,VIP] 的请求后,产生一个 IPIP 的包 [src=CIP, dst=SIP, protocol=IPIP, payload=[src=CIP, dst=VIP] ]。这时候,由于我们还没有从服务端正确地配置 IPIP Tunnel 和 VIP, 所以这个包无法正确地被识别和处理, 导致了 3 次 SYN 包的重传和 1 次 RST (图 6 中的包 3,5,7 和 9),同时服务端以 ICMP 的形式告诉 client,由于目标 IP 地址无法抵达的缘故,之前的包无法被正常传递 [5](图中包 2,4,6,8,10)。

图 6 LB 通过 IPIP 隧道发到服务端的数据包

Step 3

进一步分析,由于服务端的内核还没有加载 IPIP 模块,所以它不能识别 Protocol=IPIP 的数据包,无法解析出嵌入在 IPIP 包里的内层 IP 数据包。由于 VIP 只出现在内层 IP 包里,因此以 VIP 作为 filter 抓包,自然是抓不到了。 现在我们尝试让内核加载 IPIP 模块并再次抓包,看它能否识别并解开内层 IP 包

我们重新做一次表 1 的 telnet 和 tcpdump,相比于上一次的抓包,这次我们以 VIP 作为 filter 就可以抓到内层 IP 包了。 这说明内核加载 IPIP 模块后就可以识别 Protocol=IPIP 的数据包了 。 但到这里,由于我们并没有在服务端的任何网络接口 (interface) 上配置 VIP,所以服务端仍然未作出回包处理。

Step 4

下一步,就是将 VIP 配置到 tunl0 或者 lo 上,让内核识别到这是一个合法的 IP 地址。 在 F5 提供的参考配置文档 [6] 中,就是将 VIP 配置到 loopback 网卡 lo:1 上,并在 tunl0 上配置 SIP。但是仔细思考一下,F5 官方提供的这个配置并不令人十分满意——根据我们前边的分析,我们只要在 loopback 或者 tunl0 这类本地网卡上配置 VIP,让内核认为只是一个合法的地址就行了,为什么还要多此一举地往 tunl0 上配置 SIP?

既然有这个疑问了,我们索性逐步推进,看看, 如果不在 tunl0 上配置 SIP 到底会发生什么?

Step 4.1

首先,我们在 lo:1 上配置 VIP,并且不在 tunl0 上配置任何 IP 地址。

再次重做 telnet,并用 CIP 作为 filter 抓包(语句见表 1),结果如图 7 所示,只能抓到客户端到服务端的请求包(奇数行为 eth0 收到的外层包,偶数行为 tunl0 收到的内层包, 总共是 1 次 SYN+3 次 SYN 重传,和 1 次 RST )。

图 7 服务端未回包

可见服务端内核确实收到了 [dst=VIP] 的包,但既没有送给应用程序,也没有回包,那是内核将这些包丢弃了吗?此时用 dmesg 命令检查一下内核日志,发现在对应时间段有 5 条 下述日志:

这个报错是什么意思呢?原来,网络攻击者(例如 DDOS)在攻击时,都会伪装自己的 IP 地址(IP Address Spoofing),以绕开 IP 源地址检查或是防止 DDOS 回包回到自身。由于源地址无法路由可达(route back),这时服务端就会不断保持连接并等待,直到连接超时,造成资源耗竭无法正常提供服务。

为了应对这种攻击,Linux 内核加上了一个叫做 反向路径过滤(reverse path filtering [7])的机制,这个机制会检查,收到包的源地址是否能从收包接口 (interface) 路由可达,如果不可达,就会将该包丢弃,并记录 “martian source” 日志 。回到我们问题的场景,tunl0 在收到内部 IP 包 [src=CIP, dst=VIP] 这个包之后,就会去尝试验证从 tunl0 这个接口能不能连通 CIP=10.218.98.18,由于 tunl0 无法路由到 CIP,所以出现了上述的报错。

那有什么办法,既能够保证一定程度的安全,又可以支持 DSR 这种场景呢? rp_filter 这个参数有 3 个 值 [8],如图 8,其中 Loose mode 下,只要从任何接口可以路由到源 IP 地址(而不限于收包接口),就不做丢弃处理。所以, 我们可以将 tunl0 的 rp_filter 设置成 2 ,这样由于 CIP 可以经由 eth0 路由,验证应该会成功。

图 8 Linux 内核关于 rp_filter 的定义

Step 4.2

如下所示, 设置 tunl0 的 rp_filter= 2 ,也就是对从 tunl0 进入的 IP 网络包,如果其源 IP 地址可经由机器上任意网卡 (例如 eth0) 路由到,就认为它是一个合法的报文。

然后重新做一次 telnet 和 tcpdump,奇怪的是,我们依旧没有看到服务端内核回包,并且内核日志仍旧报 “martian source” 的错误。这又是怎么回事呢?看来,F5 官方文档让在 tunl0 上配置 SIP 的要求似乎是不能省略的。

Step 4.3

接下来我们往 tunl0 上配置 SIP,然后重做 telnet 和 tcpdump。不出所料,此时服务端给出了正确的 DSR 回包(限于篇幅,此处略去抓包结果)。

到这里,DSR 误打误撞地完成了配置,但我心里还有些难以平复—— Tunl0 网卡的作用在于接收 IPIP 隧道内的内层 IP 包,但是服务端内核在回包时,是直接将 [src=VIP, dst=CIP] 的 IP 包经由 eth0 路由出去的,根本就没有 tunl0 什么事。 所以 tunl0 上理论上不需要配置任何 IP,但为什么在 tunl0 上配置 SIP 以后,就能解决 “martian source” 的问题呢?

怀着这个问题,我在网上找了很久的答案都没找到,最终通过 eBPF 对 linux 内核源码进行分析, 发现在 linux 的 rp_filter 代码中,如果网络接口(这里是 tunl0)不配置任何 ip(ifa_list == NULL),那么就会返回验证失败。

具体请参考 github 链接:

https://github.com/centurycoder/martian_source

Step 5

既然 linux 内核要求 tunl0 上必须配置 IP 地址,那是不是意味着我们必须按照 F5 提供的官方步骤进行配置呢?不是的,我们前面已经分析,只要是在 loopback 或者 tunl 网卡上配置 VIP 骗过内核就行, 那我们就索性将 VIP 直接配置到 tunl0 网卡上,这样既实现了 tunl0 上有 IP 地址的要求,又满足了 VIP 是一个合法目标地址的要求 。 我们将第 4 步的配置回退后,在 tunl0 上配置 VIP 和 rp_filter,如下所示:

然后按照表 1 重新做一次 telnet 和 tcpdump,如图 9 所示,客户端和服务端通过 DSR 成功完成 TCP 的 3 次握手( 1 是 eth0 端口上抓到的外层包, 2 是 tunl0 上抓到的内层包,它们其实是同一个 SYN 包 ,所以被 wireshark 标记为 TCP Out-of-Order; 3 是服务端回复的 SYN/ACK 包 ,它不走 Tunnel,所以没有成对出现; 45 是客户端发的 ACK 包 ,分别是 eth0 抓到的外层包和 tunl0 抓到的内层包,被 wireshark 误认为是重复的 ACK)。

图 9 成功的 DSR 通讯

四、总结

到这里,我们就完成了 DSR 的原理分享和探究,并且通过对原理以及 Linux 内核源码的分析,对 F5 官方提供的步骤进行了解析和优化。总的来说,从运用方面来看, 相比于 SNAT 模式,DSR 在提高 LB 带宽容量、保持客户端真实 IP 等方面有很大优势 。 但在选择 DSR 或者 SNAT 模式时还要考虑实际运用场景。 DSR 往往更适用于 LB 仅用作负载均衡功能的场景 ,而如果 LB 还承担 SSL offload、Cache、加速或者其它安全相关功能时,则只能选取经典 SNAT 模式 [9]。希望本文能对各位读者理解和运用 DSR 技术有所帮助和启发。

参考资料 :

[1] https://kemptechnologies.com/au/white-papers/what-is-direct-server-return/

[2] https://www.loadbalancer.org/blog/15-years-later-we-still-love-dsr/

[3] https://www.haproxy.com/support/technical-notes/an-0053-en-server-configuration-with-an-aloha-in-direct-server-return-mode-dsr/

[4] https://developers.redhat.com/blog/2019/05/17/an-introduction-to-linux-virtual-interfaces-tunnels/

[5] https://tools.ietf.org/html/rfc792

[6] https://techdocs.f5.com/en-us/bigip-15-0-0/big-ip-local-traffic-manager-implementations/configuring-layer-3-npath-routing.html

[7] https://www.slashroot.in/linux-kernel-rpfilter-settings-reverse-path-filtering

[8] https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt

[9] https://devcentral.f5.com/s/articles/the-disadvantages-of-dsr-direct-server-return

本文转载自公众号 eBay 技术荟(ID:eBayTechRecruiting)。

原文链接 :

https://mp.weixin.qq.com/s/vHQpnGnrc15D2HjcO8XIJA