写点什么

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

  • 2020-04-17
  • 本文字数:6164 字

    阅读完需:约 20 分钟

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


2020-04-17 14:052523

评论

发布
暂无评论
发现更多内容

react源码分析:组件的创建和更新

flyzz177

React

前端leetcde算法面试套路之树

js2030code

JavaScript LeetCode

限流算法及方案介绍

京东科技开发者

高并发 限流算法 高并发处理 限流熔断 企业号 2 月 PK 榜

从零开始实现一个Promise

helloworld1024fd

JavaScript 前端

从零到一手写迷你版Vue

helloworld1024fd

JavaScript 前端

京东前端二面高频手写面试题(持续更新中)

helloworld1024fd

JavaScript 前端

react的useState源码分析

flyzz177

React

你是如何使用React高阶组件的?

beifeng1996

前端 React

面试官:vue2和vue3的区别有哪些?

bb_xiaxia1998

Vue 前端

说说你对Vue的keep-alive的理解

bb_xiaxia1998

Vue 前端

用javascript分类刷leetcode13.单调栈(图文视频讲解)

js2030code

JavaScript LeetCode

appuploader上架详解大全(上)

雪奈椰子

iOS上架 ios打包 IPA上传 app证书

appuploader 上架详解大全(下)

雪奈椰子

iOS上架 描述文件打包

从React源码来学hooks是不是更香呢

goClient1992

React

Gateway集成Netty服务

Java 架构

全面了解Python的变量与基本数据类型

Python 变量 数据类型

从React源码角度看useCallback,useMemo,useContext

goClient1992

React

从基础掌握Python的列表和元组

Python 列表 元组

前端leetcde算法面试套路之堆

js2030code

JavaScript LeetCode

带你实现react源码的核心功能

goClient1992

React

巧用 ChatGPT,让开发者的学习和工作更轻松 | 社区征文

陈明勇

ChatGPT

ChatGPT:时代赋予的机遇 | 社区征文

石云升

人工智能 机遇 AIGC ChatGPT

阿里前端二面react面试题

beifeng1996

前端 React

深度讲解React Props

夏天的味道123

前端 React

巧妙利用“慧言”机器人在安全场景中实践

京东科技开发者

人工智能 AI 技术 场景需求

Python字符串和正则表达式的深入学习

Python 正则表达式 字符串

Python字典和集合初窥

Python 集合 字典

百度前端一面高频vue面试题汇总

bb_xiaxia1998

Vue 前端

2023前端vue面试题及答案

bb_xiaxia1998

Vue 前端

react源码分析:深度理解React.Context

flyzz177

React

滴滴前端一面高频手写面试题汇总

helloworld1024fd

JavaScript 前端

eBay流量管理之DSR在基础架构中的运用及优化_架构_eBay技术荟_InfoQ精选文章