一探究竟 | eBay流量管理之DSR在基础架构中的运用及优化
更多干货请关注“eBay技术荟”公众号
导读
本文对负载均衡器的DSR(Direct Server Return)模式进行了原理分析,并以业界普遍使用的F5 LTM+CentOS为例,探究了DSR配置的关键技术点,最后借助Linux内核分析,对F5提供的官方步骤提出了优化方案。希望能对读者有所启发和帮助。一、背景
负载均衡的常用模式可概述为下(详情可见:分享 | eBay流量管理之负载均衡及应用交付):
1. 客户端向负载均衡器提供的虚拟IP地址VIP发送请求(CIP → VIP);
2. 负载均衡器从提前配置的多个子网IP地址中选择一个(SNAT IP),替代客户端请求的源IP,并依据负载均衡算法,选择一个服务器IP作为目的IP,发送请求 (SNIP → SIP);3. 服务器处理后将响应结果发回负载均衡器(SIP → SNIP);4. 负载均衡器将最终结果返回给客户端(VIP → CIP)。1. 由于信息量的因素,网络请求的回包往往会比请求包大很多。一般达到10倍[1],20Mbps的请求,其回包可能达到200Mbps。这样一来,由于回包也是经过LB,就会大量增加LB的带宽使用,减小LB的有效处理容量。
2. 基础架构的服务(DNS,MAIL,LDAP等)工作在TCP/UDP传输层之上,所以无法像其它工作在HTTP协议以上的应用那样,用HTTP header里边的X-Forwarded-For字段来保存客户端真实IP。这些基础架构服务的请求包在被LB进行SNAT之后,客户端的真正IP被替换为LB的SNAT IP。这样造成的结果就是,后端服务器无法知道真实的客户端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)。事实上,这种做法就是网络隧道(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协议主要有IPIP和GRE两种,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
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
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,所以没有成对出现;4和5是客户端发的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大量优质职位,等的就是你