1. 程式人生 > >OpenVPN優化之-巨型幀

OpenVPN優化之-巨型幀

ace 通過 tap模式 配置 ip分片 內核 大數據 特殊 復制

近幾日忙過了頭,一直糾結於OpenVPN的性能問題,這實在是個老問題了。幾年來一直都是修修補補。直到多線程多處理的實現,攻克了server模式服務端的吞吐量問題,使得多個CPU核心能夠得到充分的利用。可是對於client的優化,一直都沒有非常好的解決方式。
或許,粗獷的作風實在是非常適合服務端優化,而client優化須要的卻是對仔細入微的細節之關註。而我,實則一個得其意而忘其形之人,實則不太適合做細活兒,然而我卻以前用廢舊的牛仔褲縫制過一款時尚手袋,表裏不是那麽如一。對於OpenVPNclient優化這件事,我遇到了“巨型幀”這個術語。

效果還不錯,測試的結果還比較愜意。還是老樣子,記錄一些想法卻不記錄技術細節,這是為了讓自己或者別人日後看到這篇文章後,知道有這麽個事卻又不能直接拿來就用,這有什麽優點呢?這會讓自己好好地再次理一遍思路而不是拿來主義的復制命令或者代碼。

自己寫的代碼或者命令,一周後,可能就和自己沒關系了。半年後,自己也不懂了...可是想法是永恒的,我依舊記得小學四年級的時候,我寫的一篇關於巴士底獄的過於早熟的短文...

近期不喝酒了,由於時間不等人。喝酒之後就會早早睡去,什麽也幹不了了,晚上夜深人靜的時候,看看歷史書,寫寫博客比喝酒好。

以太網的一點歷史包袱

以太網自打出生之日。一直保持著向下的兼容性。兼容性這個計算機時代可謂最重要的術語在以太網可謂表現得淋漓盡致,全然能夠和IA32以及Win32 API相媲美。滿足了投資者的心理的同一時候。方便了消費者。然而對於技術本身,保持兼容性卻如臨大敵。
10M以太網時代。對於幀長是有規定的。最大的幀長和最小的幀長都是能夠依據線路的物理特性計算出來的,最大的幀長的計算結果是1500Byte。於是,這個1500就成了一個以太網MTU的默認設置。在10M年代,這個長度絕對不短了,實際上它是能夠發送的最大長度,超過這個長度的上層數據都要在IP層被分片。然後在接收端重組分片,而這個過程無疑須要很多其它的處理器時間,消耗資源。為了避免IP分片。傳輸介質的MTU必須被IP上層的協議所感知,對於TCP而言,這就是MSS協商,細節我就不講了。MSS協商之後。能夠保證TCP每次發送的數據的長度均小於介質的MTU,對於UDP而言,須要應用層自己管理數據報的長度。或者通過MTU發現機制來動態調整。無論怎麽說,IP分片能避免則避免。


在下一節我會描寫敘述MTU確定的細節。如今我們知道它在兼容的意義上是1500,其實。在10M年代,這個1500是電氣特性決定的,可是在10G年代,以太網經過了天翻地覆的變化,1500早就不再是電氣特性無法跨越的坎了。因此1500剩下的僅僅是一個軟件含義,你全然能夠設置它為15000,可是即使如此。僅僅要數據經由的路徑上有一段路經的MTU是1500。那麽就會發生IP分片。這就是歷史包袱。能夠發送更長的數據,可是卻無法發送,由於發送了便可能產生IP分片。而IP分片的處理和重組可能會抵消掉大型數據幀帶來的空間節省和時間節省。

分組交換的兩個極端

為了避免IP分片。網卡總是被期待發送最小的數據。可是為了數據包處理效率的最大化。網卡總是被期待發送最大的數據,這就是一個矛盾。須要代償計算權衡。我們知道。分組交換網的每個分組均要攜帶元數據。由於協議棧是分層的,對於每個分組,都要封裝多個不同層的協議頭。


分組交換網,它是除了電路交換外的還有一種傳輸形式,可是你能夠換一種理解方式而調和二者。假設你把一個整個的數據流封裝到一個分組,那就是電路交換,該數據流僅僅有一個分組,一個分組僅僅能有一條路徑,這條路經在分組傳輸前就是客觀存在的;假設你把一個整個的數據流拆分成多個分組,那就是分組交換。對於每個分組,它們經過網絡的路經可能是不同的。而在一個時間段內。一段路經上可能會經過不同數據流的分組。統計復用即體現於此。


假設分組過小,盡管能夠避免IP分片。可是同一個數據流直到處理完成須要的數據分組會過多,因此會有很多其它的數據空間消耗在元數據上,同一時候很多其它的處理器時間消耗在依據協議元數據來進行的路由與交換上,假設分組過大。盡管會有更少的空間和時間消耗在協議元數據上。可是會有IP分片重組的開銷,註意,在分組交換網上。IP分片是數據分組超過一定長度後必須的,這是由線路物理上的電氣特性決定的,假設非要傳輸超長的數據分組。將會出現電氣層面的故障。比方脈沖畸形等。

因此能發送的避免全程IP分片的最大數據長度便是全程每一段路經MTU的最小值。

合時宜的巨型幀

盡管為了兼容。1500依舊是諸多以太網卡的默認MTU設置,可是廠商對如今1G/10G等高端網卡以及超五類/六類雙絞線以及光纖的高大上特性又不能視而不見,因此保留了對MTU的配置接口,由用戶自己來決定自己網卡的MTU值,超過1500Byte的以太幀,為了和原汁原味標準以太網的1500Byte差別,叫做巨型幀,名字挺嚇人,實際上也沒有巨型到哪去。

用戶全然能夠設置自己的網卡的MTU超過1500,可是能到多少呢?
標準化問題是又一個棘手的問題,由於在交換以太網環境,線路的特征不再僅僅能夠通過網卡和單一標準線纜(比方早期的粗纜。細纜)的電氣特性來計算,而和交換機配置,雙絞線類別,質量,網卡自協商配置,雙工模式等息息相關,所以盡管出現了巨型幀。它還真不好駕馭,假設發送巨型幀,中間MTU偏小的窄路由器會將IP分片不說,可能還會影響對端數據幀的接收。由於連接兩臺設備的線纜非常可能不是一根線纜。即便是一根線纜。兩端的網卡特征也不一定同樣,這就會導致收發設備對電氣特性的解釋有所不同。導致數據幀的接收失敗。
無論怎麽說,巨型幀是合乎時宜的。假設沒有1500作為底線。巨型幀長的標準化進程會快非常多。


將OpenVPN當作一種介質

前面說了那麽多看似和OpenVPN的優化不相關的東西。其實是非常相關的。

假設你到如今還不明確OpenVPN的處理流程。請看我之前文章的關於OpenVPN的結構圖,在那附圖裏,我把OpenVPN當成了一種介質。數據從tap網卡發出進入了字符設備,這就進入了這樣的特殊的介質,等數據加密後發往對端。解密後寫入字符設備,然後被對端tap網卡接收,這就走出了這樣的特殊的介質。
既然是一種介質,那麽肯定有自己的“難以跨越的電氣特性”了。這樣的電氣特性告訴人們它是怎麽數據傳輸幀的。對於OpenVPN來講,它的電氣特性就是對數據進行加密。解密。

在詳細實施上,和在銅線上數據傳輸幀時的前導脈沖一樣。OpenVPN也會在tap網卡出來的數據幀前封裝一個所謂的“前導脈沖”。這就是OpenVPN的協議頭。我們來看一下這個特殊的OpenVPN介質的傳輸開銷。

開銷分為三大塊,空間開銷,時間開銷。系統開銷。
在空間上,OpenVPN協議頭無疑占領了一定的數據空間。假設tap網卡的MTU是1500。那麽加上OpenVPN協議頭的長度L,那麽OpenVPN數據通道的數據最大長度將會是1500+L。我們知道這個數據作為一個buffer是通過UDP(暫且不談TCP)套接字傳輸至物理網卡的,假設物理網卡的MTU也為1500,那麽總長度1500+L+UDP/IP協議頭的最大長度則肯定超過1500,而這將導致物理網卡上的IP分片,數據到達對端OpenVPN後首先要進行IP分片重組。然後再進入OpenVPN,解封裝。解密...假設tap網卡的MTU過小。且遠遠小於物理網卡的MTU。盡管加上OpenVPN協議頭。UDP/IP協議頭可能只是超過物理網卡的MTU而不會IP分片,可是其載荷率將會大大降低,由於對於同樣長度的數據流片斷,會有大量的數據空間浪費在OpenVPN協議頭,UDP/IP協議頭上。反之。假設tap的MTU過大,載荷率將會大大提高,可是會引發IP分片。

在分析完時間開銷和系統開銷後。我會說一下結論。現代高端網卡的IP分片與重組開銷能夠忽略。


在時間上,OpenVPN的加密/解密。HMAC,壓縮/解壓縮等要消耗大量的CPU時間。經過測量,頻繁加密小包的效率將會小於一次性加密大包的效率,延時效率不會有太大變化。而對吞吐率而言。大包加解密效率會明顯提高。

這個細節盡管不是什麽cache在影響。倒是和CPU的流水線處理相關。細節就不多說了。特別是在RISC處理器上。效率更高。
在系統開銷上。由於OpenVPN要經由tap字符設備和tap虛擬網卡交互,通過socket和對端交互。因此系統調用read/write,send/recv將會導致用戶態和內核態的切換。而這樣的切換的數量會隨著數據分組的增大而降低。
如今已經非常清晰了。那就是將tap虛擬網卡的MTU設置成超級大!
那麽我們難道不怕OpenVPN和對端通信的socket發送數據過大而導致的IP分片嗎?是的,我們寧可讓它在本機進行IP分片,也不會通過物理網卡的巨型幀將其發送出去,第一,我不知道它是否能被鏈路對端的設備正確接收,第二,我也不曉得整個路徑上將巨型幀攜帶的數據分組進行IP分片的那臺設備的處理效率。可是我對自己的這臺設備還是能駕馭得了的。它擁有Intel825XX千兆卡,帶有TSO功能。即TCP Segment Offload(相似的UFO,LRO,MS煙囪卸載等等)。使用硬件進行IP分片,能夠想象,這樣的卡的處理性能是超級的。和中間的路由設備不同的是,我的設備我了解,對端OpenVPN也擁有相似的網卡硬件配置(是發送巨型幀呢?還是使用硬件的Offload功能就地分片,我想你應該能夠權衡所以了吧)。因此IP分片的開銷能夠忽略。

優化實施中的建議

我將tap網卡的MTU設置成超級大,它得以實用武之地的前提是。你得有那麽大的數據報文從tap網卡發出,假設兩個OpenVPN端點僅僅是兩個網絡的邊緣節點,那麽你不能指望數據的出發地一定發出了一個巨型幀。說不定數據出發的端點的MTU僅僅有1500,那麽無論怎樣數據到達tap網卡的時候。最多僅僅有1500Byte(假設中間經由一個MTU僅僅有500的路由器轉發,它將會被分片。每個片斷長度更短),因此建議數據出發的端點均發送巨型幀。
假設數據端點發出了巨型幀,可是在到達OpenVPN節點的擁有超級大MTU的tap網卡之前被分片了怎麽辦?好辦,在軟件上,你能夠使用Linux載入一個nf_conntrack_ipv4的模塊,由於它依賴defrag模塊,所以但凡分片的數據報文都會在forward到tap網卡之前重組。

或者說。挖掘一下網卡硬件的自己主動重組機制(據我所知,好像發往本機的數據在硬件芯片中重組實現更easy些)。
請永遠不要指望將多個短的IP報文合並成一個長的IP報文。特別是對於上層協議是UDP而言的數據報文,由於這會使UDP數據報的邊界錯亂,進而在應用層發生語義錯誤。IP本身對於分片和重組的處理是不正確稱的,它能識別哪些分片屬於同一個分組,可是一旦重組就無法依照重組前的原樣再次分片,分片的依據僅僅有MTU!

正如你能將N堆的豆子攪和在一起,卻再也無法將其分離一樣。對於TCP而言,盡管這麽做沒有問題,可是你必須進行復雜的處理,比方維護一個哈希表,然後將屬於同一個五元組的IP全報文(非分片報文)組成適當長度的新的IP報文,同一時候跟蹤總長度信息,不能超過tap網卡的MTU。這個算法一定要高效,否則將會抵消組合處理大分組帶來的優點。

事實證明,無論在內核實現這個算法還是在OpenVPN本身實現這個組合算法,都是困難的。


對於P2P模式的OpenVPN而言,在tap網卡外準備一個和tap網卡MTU大小一樣的箱子是個好主意,其實就是進行一層新的封裝,由於反正我知道無論什麽數據報文僅僅要經由tap網卡。都是送往唯一的對端去的,因此也就不須要再區分五元組標識的流了。小IP報文來了就塞進箱子。箱子僅僅要塞滿就蓋上蓋子扔進tap網卡,這個主意不錯。可是對於server模式卻無能為力,其實,對於server模式。你要準備N個這樣的箱子。N等於OpenVPNclient的數量,每個IP報文在進入服務端tap網卡前。第一步就是查找自己將要進入的箱子,不說了,我實現的多隊列tun網卡已經實現了這個查找。
為了取得更高的空間效率,同一時候也是時間效率。我建議使用tun模式替代tap模式,盡管在網到網部署模式下須要配置復雜的iroute指令(畢竟IPv4沒有強制必須使用自己主動配置,其實它也沒有!DHCP是一個極其不完備的蹩腳協議!)。
最後談一下Windows平臺的TAP-Win32網卡的MTU改動問題。

假設你右鍵點擊TAP網卡的屬性,配置其MTU為超過1500的隨意值。會得到錯誤提示,不同意超過1500!

可是其實你確實能夠在OpenVPN的配置文件裏寫上tun-mtu 9014這樣的指令,也不會報錯,可是在實際執行的時候。它並沒有採用9014這個MTU。而毅然(這不是“依舊”的別字。而就是毅然!)是1500!那麽怎樣才幹配置TAP網卡的MTU為所謂的“巨型幀”的大小呢?答案是使用netsh。
執行netsh interface ipv4 set subinterfaces "TAP..." mtu=9014 sore=persistent這個命令,你的TAP網卡的MTU將會改變為9014,能夠用netsh interface ipv4 show subint查看。

可是此時你看一下TAP網卡的屬性,依舊是1500!無論怎樣,它確實能發送長度為9014Byte的巨型幀了。

我已經不想再吐槽了!總之,微軟的接口就是不好用。而且喜歡將簡單問題復雜化。

假設你想改變一下TAP的MTU,不但你要改動配置文件,還要使用netsh將其“真正地改動掉”!

代償

IP分片的開銷。協議頭的開銷。這是矛盾的二者。

矛盾的他者卻是為了構造tap網卡的巨型幀。先在tap重組,然後加密封裝完成後在物理網卡又一次分片。孰重孰輕。自己掂量。


鎂光燈照亮了舞臺卻驅不走孤獨。或許為賦新詞強說愁的文藝青年真的須要一點閉上眼睛就是天黑的感覺。
100年前,你上班要走路一個小時,如今你上班依舊須要一個小時,僅僅是在車上,動力的發展總是讓你和目標的距離越來越遠。


孰重孰輕,自己掂量,這就是代償。出來混,早晚要還的...

OpenVPN優化之-巨型幀