供稿 |  eBay Infraops Team

作者 | 梅南翔

審稿 | 楊勝輝

編輯 | 顧欣怡

本文4536字,預計閱讀時間14分鐘

更多幹貨請關注“eBay技術薈”公衆號

導讀

本文對負載均衡器的 DSR(Direct Server Return)模式 進行了原理分析,並以業界普遍使用的F5 LTM+CentOS爲例,探究了DSR配置的關鍵技術點,最後藉助 Linux內核分析 ,對F5提供的官方步驟提出了優化方案。希望能對讀者有所啓發和幫助。

一、背景

在現代企業內部, 負載均衡器 (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,所以沒有成對出現; 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

您可能還感興趣:

乾貨 | Rheos SQL: 高效易用的實時流式SQL處理平臺

分享 | “三高”產品設計的這些坑,你是不是也踩過?(上)

分享 | “三高”產品設計的這些坑,你是不是也踩過?(下)

一探究竟 | eBay流量管理之看不見的手

解密 | 一樁由數據潔癖引發的DNS懸案

分享 | eBay流量管理之Kubernetes網絡硬核排查案例

分享 | eBay流量管理之負載均衡及應用交付

:point_down:點擊 閱讀原文 ,一鍵投遞 

eBay大量優質職位,等的就是你

相關文章