1. 程式人生 > >iptables高效能前端優化-無壓力配置1w+條規則

iptables高效能前端優化-無壓力配置1w+條規則

產生本文的故事

顯然是個週末,這是一個下雨的週末,這是一個颱風登陸的週末…

  上週,有一個很猛的颱風“天鴿”,當天紅警晚發了兩個小時,當釋出紅警時,幾乎所有人都到了公司,當然,除了那些怕西裝和皮鞋被雨淋溼的經理。我本來是想特別感謝一下這個颱風的,然而它已經造成了重大的人員傷亡,無論如何,我都不能再對它多說一句褒義的話,我是一個喜歡風雨的人,僅詮釋我個人。

  在臺風“天鴿”將至的那晚,我把一個在暴熱和加班中被遺忘擱置的問題重新拿了出來,開始了一番探究…

  就在三天後,颱風“帕卡”將至,我寫了本文。所以我把本文定位為兩個接踵而至的颱風刺激下寫的一篇技術散文,而不是技術指導,技術指導性的文件需要相當的理性,而我現在只有狂風暴雨,和酒。

動機

有人諮詢我關於幾千條iptables規則造成收包效能急劇下降的問題了,iptables再次背了鍋。

  自從2009年底開始,就不斷有人天天強調基於Linux核心Netfilter的iptables以及Netfilter本身會造成效能下降,這種聲音最後一次聽是在昨天下午!

  這麼多年來,發出這種聲音的很多人甚至根本就理不清iptables和Netfilter的關係,更是對其內部原理一無所知,大部分都是人云亦云,也不知道是誰第一個說的,反正後面就跟著把故事流傳了下來。也許第一個這麼說的是國外一位大牛,針對特定場景提到了這麼一個結論,很多人斷章取義就認為這是在否定iptables本身…

  我被很多公司面試過,也面試過很多別人,在我被問及關於Netfilter相關的問題時,我知道他們的意思是想知道如何優化它,天啊,你自己可能都不知道Netfilter的問題在哪,何談優化,不破哪有立…當我面試別人的時候,我也會問關於Netfilter相關的,但我的問題往往是:你覺得Netfilter有問題嗎?

  我不止一次地強調過,iptables沒有錯,Netfilter也沒有錯!

  是的,你可能用“幾千條iptables規則確實造成了效能下降”,“一開啟conntrack效能就會下降”這種來反駁我,但我馬上就能懟回去:這是Netfilter的問題嗎?然而這是iptables的問題嗎?

  規則配的太多導致效能下降是因為你iptables玩的不精!

  conntrack造成效能下降那是conntrack自己的問題,為什麼讓Netfilter背鍋?!

  我之前寫過幾篇關於nf-HiPAC的文章,它即使配置20000+條規則也不會造成收包效能下降,然而它也是基於Netfilter的。更具諷刺意義的是,你們可能對APPEX(華夏創新)這個公司有所耳聞,是的,它是做加速的,請注意,我在強調“加速”二字,人家的加速竟然用了Netfilter!雖然彼效能不是此效能,一個是TCP的效能,一個是主機CPU效能,但為了榨取哪怕0.1%的CPU效能,如果Netfilter有大問題,APPEX會用它嗎?

  即便你認識到了問題出在iptables而不是Netfilter,你還是有別的選擇的,比如你可以試試nf-HiPAC,不過我敢保證你不一定能玩得起來,這玩意兒資料很少,中文的估計也就我那幾篇了…另外的選擇就是用ipset,這個很常規,也很樸素,它只是一個iptables的模組,在你的match是IP地址的情況下,使用ipset完全可以解決幾千條規則導致的效能下降問題。

  我知道你不想重寫規則,更不想去編譯安裝什麼nf-HiPAC,ipset之類的新東西,你只想保留你的8347條iptables規則,然後用很有點拿來主義經理的口吻問我:別的我不能動,但是效能下降必須控制在10%以內。

  雖然這並不關我毛事,但還是感覺受到了威脅,還好,感謝颱風“天鴿”,“帕卡”,讓我有了一個新的思路,解除了威脅。

宣告:

2.本文不是一個關於新技術的HowTo,它只是一個新想法,有很多TODO。如果問這東西的程式碼在哪裡,那隻能等溫州皮鞋廠老闆了。

已有的優化方案

開本文的背景(比如不能修改規則集之類的),如果你確實覺得iptables效能很不行,然後希望優化的話,你做的第一件事可能就是去google或者去百度,你會發現一系列現成的方案,比如:

  • 使用state match

比如你可能會加入下面的一條規則在所有規則最前面:

iptables -A INPUT -m state --state ESTABLISHED -j ACCEPT

你期望著用流而不是資料包來進行匹配,但你可能不知道的是,流依賴了conntrack機制,而該機制也是飽受詬病的因素之一。

  • 使用自定義chain

在很多論壇上,有很多人早就提出了使用-N選項建立不同的chain,然後肉眼歸類所有的規則,儘量把具有同一性的規則分配到單獨的chain中,這樣整個規則集就分層了。
  這正是本文要描述的模型的樸素版本。

  • 使用nf-HiPAC或者ipset

這個就不多說了,效果相當好!

這裡給出一個關於iptables效能測試的連結,可以關注一下:
Netfilter Performance Testing
值得一提的是,在這篇文件中,提及了一些優化手段,其中包括一個叫做Compact Filter (CF)的專案,我覺得是比較有意思的,然而其連結已經失效,後來找到了一篇論文:
An Interval Decision Diagram Based Firewall
這個設計正是採用區間匹配來快速定位Action的,跟我將要通篇描述的方案几乎是一致的,感興趣的可以看一下。

其實,iptables如何優化,方案几乎也就兩類:

1.重寫核心過濾機制

比如nf-HiPAC,ipset之類就屬於這類。

在使用者態優化規則集

比如Compact Filter (CF),精心設計自定義chain跳轉,以及本文描述的n+1模型就屬於這類。

  如果上面這些你都覺得太簡單或者說已經看過了瞭解過了,那麼本文剩餘的部分將要介紹的這個可能是你第一次聽說。

優化方案補遺

在我終於完成了這篇冗長的文章後,我順勢google了一下關於iptables優化的一些枝枝蔓蔓,希望能找到一些好玩的東西,如果能找到,那簡直是比吃一頓大餐還要更爽的,加上外面狂風暴雨,舒坦地難以言表。

  我還是找到了,我找到了下面這個:
Optimizing the Interval Decision Diagram Im-plementation in the CF Firewall
我不得不說,快速掃完了這個之後,我是激動的(在完成本文前,我一直想找到有關Compact Filter (CF)的資料,但沒有找到)。這份資料幾乎完整地闡述了一個跟我的n+1模型一模一樣的方案的優化版本,同時在這份資料的開頭表明了Compact Filter (CF)是一個非常大眾化的方案,不然的話,為什麼大家都往一起想呢?

  雖說這份資料讓我希望成為首先提出類似想法的那批人成為了泡影(同樣的方案出現於2005年之前!那時我還才剛剛參加完高考…),然而,令我依然感到欣慰的是,至少我用一個直觀的立體幾何模型而不是抽象的代數模型來完整地闡釋了這個思想,這簡直和前端之間寫關於BadVPN,無理數本質以及泰勒級數時的方法有著異曲同工之妙啊!

Anyway,讓我們開始吧!

效能前端封裝

接觸iptables已經很多年了,第一次接觸是2006年的冬天,然而2006年到2010年底這期間,我也只是聽過這個東西並且會簡單使用而已,從2010年底開始才逐漸深入。在這些年裡,我接觸到了太多的Linux防火牆,其中不乏成型的產品。從OpenWRT中簡易的Firewall模組到Endian Firewall,隨著UTM越來越多的自帶了防火牆的功能,Linux防火牆這個詞所表示的意義就更加廣泛了。

  然而萬變不離其宗的是,所有這些都是對iptables的前端封裝,有的基於Python,有的用Go,更多的是用Web,所有這些iptables前端都是功能性前端,沒有專門的效能前端,這也許是因為在大多數場景下,按照常規方式新增的iptables規則跑得還不錯(幾乎沒有人用iptables去配置幾萬條規則),至少沒有出現嚴重效能問題,那麼大家不關注也是理所當然,萬一出了效能問題,那就只能見招拆招。

  然而,功能性前端則完全不同,它是剛需!因為很少有使用者願意去熟悉或者學習iptables命令的用法,人們希望有一個直觀的UI,比如WebUI,GUI,用最快最直觀的方式去配置防火牆而不是研究防火牆的原理

  本文的思路主要是想做一個很少見的針對iptables的效能前端封裝,它串接在功能性前端封裝後面,將功能前端的iptables規則輸出作為效能前端的輸入,最終輸出一套優化後的iptables規則集並注入核心。大致的圖示如下:

這裡寫圖片描述

可行性

經,每一條iptables規則都是獨立的,規則的順序決定了匹配的順序,這樣的話,核心只能是遍歷匹配,因為它看到的就是一條條獨立的規則組成的一個規則鏈,如果你添加了100條規則,那麼核心只能先匹配第1條規則,然後第2條,然後第3條…如果足夠不幸的話,你要匹配到第100條規則才會發現根本沒有一個匹配到的,然後無奈地去執行Default Action

  我記得在我2010年剛剛找到一份新工作入職之後,就從領導那裡得到一個資訊,如果配置10000條iptables規則,PPS效能會急劇跌落。但當時還不是很懂iptables的原理,所以也只是記住了這個結論,待到後來終於理解了iptables/Netfilter之後,我驗證了這個結論,確實如此!

  造成這個結論的原因很簡單,就是核心協議棧會針對每一個經過的資料包去逐一遍歷這10000條規則…

  如果把所有的iptables規則組成的集合看成是一個整體,這樣就可以多維度分析了,比如來個轉置什麼的。由此,我們就可以更細維度地去分析這些規則的特徵和分佈了。

  如果是逐條新增的獨立的iptables規則,對於孤立的一條條規則,整個過程看起來是下面這個樣子:

這裡寫圖片描述

然而,如果我們把所有的規則看成一個整體,就可以對所有的規則進行如下的分析:

這裡寫圖片描述

當然,這裡面必然還存在很多的細節,但我們先忽略這些細節,先讓我們看看這意味著什麼。我們又需要做什麼。

  我們可以先不急著把規則一條條新增入核心,取而代之的是,先對所有這些規則進行一次預處理,然後再以一種完全不同的方式注入核心

  謝天謝地,在臺風“天鴿”將至的夜裡,我想到了辦法!(在最終寫這篇文章的時候,是颱風“帕卡”將至的早上,這會兒已經風雨大作了)

  雖然在視覺上一眼看上去iptables以內建的chain作為集合的方式,但與此同時,iptables提供了使用者自定義chain以及相應的goto指令,這就意味著iptables規則不必一定按照線性的平坦組織方式來組織,而完全可以組織成一個樹型結構,比如下面的樣子:

iptables -A INPUT --match1 a -g self_define_chain1_1
iptables -A INPUT --match1 b -g self_define_chain1_2

self_define_chain1_1:
iptables -A self_define_chain1_1 --match1 c -g self_define_chain1_1_1
iptables -A self_define_chain1_1 --match1 d -g self_define_chain1_1_2


self_define_chain1_2:
iptables -A self_define_chain1_2 --match1 e -g self_define_chain1_2_1
iptables -A self_define_chain1_2 --match1 f -g self_define_chain1_2_2

...

self_define_chain_x_end:
iptables -A self_define_chain_x_end --match1 x -j XXX

它對應下面的二叉搜尋樹:

這裡寫圖片描述

看出點端倪了嗎?

  我的意思是說,將按照順序新增的一條條iptables規則(它們是線性結構)進行一系列的預處理變換,它們就可以被組織成類似上面分層的樹形結構,並且效果是等價的。然而,如何做到效果等價的呢?這要通過我的一個模型(該模型與nf-HiPAC以及DxRPro++的模型非常類似)來闡釋,請接著讀。

  我們知道,iptables處在同一個chain中的規則在核心裡是順序執行的,不同chain中的規則組成了規則樹,核心是按照深度優先的原則遍歷規則樹並執行規則的,因此依然可以看作是順序執行的,所以,為了在統一的檢視中體現這個順序,必須把順序這個維度單獨抽取出來,這就形成了一個完備的自包含多維超立方體模型

  為了理解這個模型,我得從頭說起。再次感謝颱風,不然我可能真的要頹廢下去了…

一維match匹配模型

從最簡單的說起。

  以僅有1個match的規則集為例,比如-s指示的源IP地址,我假設所有的規則都只有-s XXX這一個匹配,這樣的規則配置了10000條:

iptables -A INPUT -s 1.1.1.1 -j DROP
iptables -A INPUT -s 1.1.1.2 -j DROP
iptables -A INPUT -s 1.2.3.6 -j DROP
iptables -A INPUT -s 1.2.3.7 -j DROP
iptables -A INPUT -s 1.2.3.8 -j DROP
iptables -A INPUT -s 1.2.3.9 -j DROP
....
iptables -A INPUT -s 10.20.30.40 -j DROP

這其中的-s XXX(Source IP address)所示的match單獨標識為一個維度,而規則的順序則標識為另一個維度(這是重點),模型的一個示例圖如下:

這裡寫圖片描述

這個模型是非常簡單的,並且非常直觀,經過該模型預處理過的iptables規則集註入核心後,當資料包到來的時候,將會在規則集內執行二分查詢(隨便哪種查詢演算法都行)演算法,快速定位到自己該匹配那條規則的Action

這裡寫圖片描述

通過上圖的模型,就可以直觀地保證採用樹型組織後的規則集合和原始的線性規則集合是等價的。我感覺這是非常帥的,事實上,我知道這個思想借鑑於nf-HiPAC,但不管怎樣,我可以把iptables本身改造成高效的工具了,不用nf-HiPAC,也不用nftables!它就是iptables本身。爆炸!

  以下開啟高能模式。

多個match多維度模型–n+1模型

1維擴充套件到多維在操作上往往是簡單的,難點是你要有想象力。

  如果有n個matches,那麼就需要n+1個維度來在模型中刻畫現實,其中n個維度刻畫的超長方體表示n個matches,每一個維度就是一個match,每條規則的每一個match對映到這n個維度的某個單獨的區間,而第n+1個維度則標識了規則的順序。

  我實在是想象不出一個4維的立體影象如何刻畫,能想象的最高只有3維,其中有2個維度是刻畫match的,第3個維度刻畫規則的順序,這裡n+1中的n為2。我想新增一條規則的過程,用下圖表示足夠了:

這裡寫圖片描述

這樣,新增一條ipables規則就成了一個畫圖的過程。這很直觀,雖然我只是用了2個matches加上一個順序維度去描述了3維空間裡的規則,但多個match表述的多維空間在數學上操作上與這個是一致的,也是很簡單的。整個n+1模型用語言描述非常簡單,一條iptables規則在這個模型中就是一個n+1維的超立方體,所以這個模型我把它叫做n+1模型,其中n個維度描述n個matches,1個維度描述規則順序。

  我記得有人寫過一篇微信公眾號文章(連結不記得了,但應該能搜到),幫人從0維一直理解到11維,寫的非常好,至少我是看懂了,我的同事朋友大多數也看懂了,所以我就覺得這些抽象的東西是可以理解的,但這不是本文的主題,不過有時間我一定會專門寫一篇有關這個話題的短文的。

實際的例子

以一個圖示表述一下上面闡述的我的iptables模型,既然是圖示,能畫出圖的最多3維,因此我用2個維度表示2個match,分別為源IP地址目標IP地址,整個填充後的模型如下:

這裡寫圖片描述

我將其翻轉一個角度,同時看看重合區域如何處理:

這裡寫圖片描述

這非常簡單。

  現在我可以重複性總結一下了。

  在超立方體中,一條iptables規則就是一個小立方體,其中垂直(應該說是‘正交’)於其它match軸的那個維度邊表示規則的順序,其它的邊則表示規則的各個match對應的維度的區間。集合所有的matches構成的n維立方體,將其向上提升m個高度(m為已經新增的規則的數量),就得到了一個n+1維度立方體,這個立方體表示了該條iptables規則的全部!

n+1模型定性分析

來先來展示一組規則:

iptables -A INPUT -s IPorNET_1 -j DROP;
iptables -A INPUT -s IPorNET_2 -j DROP;
iptables -A INPUT -s IPorNET_3 -j DROP;
iptables -A INPUT -s IPorNET_4 -j DROP;
iptables -A INPUT -s IPorNET_5 -j DROP;
...
iptables -A INPUT -s IPorNET_n -j DROP;

如果說,IPorNETx1<=x<=n這n個IP地址區間都是獨立的,互相不重疊,那麼順序執行就是沒有意義的,畢竟一個數據包的源IP地址只能匹配這n條規則中的一條,不是這個就是那個,如果都不匹配,那麼就是Default Action,此時就可以直接二分檢索!或者用你任何熟悉的高效的演算法搜尋超立方體都可以。

  引申開來,就是說,只要多個(姑且設為m個)matches構成的超立方體彼此之間沒有任何重合重疊,那麼就可以直接採用多維區間二分查詢演算法來定位。據我所知,如果說是一套設計良好的防火牆規則集,那麼防止重合將是一個十分必要的需求,這也是為了以後的策略擴充套件,有沒有這個能力體現了一個運維人員的職業素養。這個工作的難度看起來並不大,但是卻能體現一個人的內力,規劃防火牆規則的意義與規劃IP地址的意義差不多一樣重要。對於規劃IP而言,你把1.1.1.1分配給非洲,然後把1.1.1.2分配給日本,這在地理上也是不明智的,因為它影響了路由聚合!

  因此,我們假設設計良好的iptables所有規則的matches超立方體都是正交的,都是相互不重合的,那麼我們要做的就是在n+1的維的空間中快速定位一個超立方體的位置。這要怎麼做呢?
  我們先來看一個直觀的例子。如何取到蘋果核?這看起來是一件沒有意義的事,但是假設有一天我就是需要蘋果核,而蘋果肉對於我而言將不再需要,取到蘋果核有幾種方法呢?在我看來,最典型的方法有兩種:

1.沿著一個方向不斷削,直到在這個方向削到蘋果核的時候再換另一個方向:

這裡寫圖片描述

這種方式有點像切西瓜。

2.沿著一個方向削一下,然後再在另一個方向削一下,始終保持手裡的蘋果是球形狀,只是球的半徑在不斷變小:

這裡寫圖片描述

這種方式和切西瓜完全不同,有點像削蘋果皮。然而那種方案更可行呢?

  我們發現,在n+1模型中,可以先沿著一個維度二分搜尋查到葉子節點,定位到該維度某個具體的區間,然後再進行第二個維度,然後第三個維度…但是也可以先沿著一個維度朝著目標逼近一點,然後再在另一個維度逼近一點,…這頗有一點KD-Tree中臨近節點演算法,這個溫州皮鞋廠老闆正在研究,我就不再細談了。總之,有一事需要告知,任何複雜的演算法在現實生活中都有原形…

  在n+1模型中,不再需要逐一的遍歷匹配,取而代之的是可以基於所有的match來做二分查詢(任意查詢均可,本文強調的是最容易實現的情形)。

  舉一個簡單的例子,一個規則集合一共有4個matches維度要匹配,一共1024,即210條規則,那麼按照常規的策略,核心需要遍歷所有的1024條規則,如果經過本文所述的預處理,1024條規則在每個match維度最多log210,即最多10次就能找到該維度的區間,4個維度總共最多需要40次即可,和1024相比,自己想想效果吧…以下是一個示意圖:

這裡寫圖片描述

其實,仔細想想,只要事先把規則集規劃好,那麼每一條規則的match幾乎都可以做到相互不重合,這就使得預處理變得非常簡單,比如如果說我配置了以下的一條規則:

iptables -A INPUT -s 192.168.10.2 -j DROP;

接下來我又添加了一條:

iptables -A INPUT -s 192.168.10.10 -j DROP;

在對這麼重複性的工作厭倦了以後,我得知192.168.10.0/25這個網段的地址除了明確禁止的之外,其它的未被啟用,於是我乾脆一勞永逸:

iptables -A INPUT -s 192.168.10.0/25 -j DROP;

請注意,這是一種偷懶的做法,因為.10.0/25和.10.2/32,.10.10/32相互之間有重合,這就需要規則的順序來決定那條規則首先起效,如果配置管理員再勤奮一些,就可以避免規則match的區間重合,所要做的就是手工把重疊的match區間拆成不重疊的區間,而這個拆分是非常直觀的。我們知道一個Net/Mask可以將IP地址空間分為三個部分:

這裡寫圖片描述

按照這個思路,再來個Net/Mask無非就是把地址空間切割的更細罷了:

這裡寫圖片描述

這樣,不管是重疊還是不重疊的原始區間,最終我們都能手動地拆分成不重疊的區間,我們針對每一個這樣的不重疊的區間編寫一條iptables規則,然後按照這些區間的分佈情況利用iptables的goto機制將這些規則組織成一棵查詢樹,一次性把最終的已然成樹的規則集註入核心,這樣不就可以避免核心線性逐條匹配了嗎?將這裡手動的做法編成程式寫成程式碼,封裝成一個前端,就是本文的主旨思路了。能手工做的Step by step的事情,就一定能寫成程式碼。

  也許你會考慮得更加周全一些,比如你會覺得“遠遠不止-s,-d這種match啊,iptables的選項和引數太多了,這樣程式寫起來會不會太複雜…”,其實這種擔心在現實的角度是不存在的。

  像我在2013年左右做的VPN裡面的那些複雜的iptables規則在現網幾乎很難碰到,我也承認,那些東西有時真的是喝了酒之後的突發奇想,要是在清醒的時候仔細斟酌,我相信肯定會有更好更簡潔的方案。

  事實上,即便是真的去寫處理10個matches的前端預處理程式,也並不複雜,因為區間拆分這件事可以共用一套程式碼,只要你能把任意match對映到有限的定義域,那麼10個matches的預處理程式僅僅是把同一件簡單的事情重複10次而已。

預處理程式的形態

態很簡單,基本就是一個管道,只有輸入和輸出介面,輸入是一套iptables規則集,而輸出則是一套完全不同的經過優化的使用goto機制的分層iptables規則集合。

  見本文“效能前端封裝”一節的示意圖。

遇到的難題以及解決

然考慮4個matches的例子,本來1000條規則,4個matches必須把全部的二叉樹嫁接在上一個match的葉子節點上,這會造成規則數量的指數級增加,空間佔用不僅僅增加了一丟丟…

這裡寫圖片描述

這是顯然的,空間換時間嘛,天下沒有免費的宴餐。然而幸運的是,我們依然可以採用各種優化手段,比如節點合併,聚合之類儘量消解不必要的空間浪費。更加幸運的是,謝天謝地,其實能讓iptables規則超過幾千條的,其涉及的match並不多,基本也就是IP地址而已,不然的話,規則的維護成本問題將讓空間問題不是個問題,此時還不如直接採購硬體防火牆呢,相信沒有任何人自己沒事找事吧。

  即便有人像我這般,真的就能寫10000條複雜的多個matches的規則集,那也好辦!

  寫個iptables的Netfilter模組即可。在描述這個方案的細節之前,我得先講一下iptables的弊端,以便理解為什麼非要自己再寫一個核心模組。

  在一開始設計這個前端預處理的時候,我在想,能不能每個match維度只寫一次匹配規則,比如對於-s $saddr這個match,不管它嫁接於哪個match的葉子節點,均採用同一個自定義chain作為該match的二叉查詢樹的樹根。然而後來我發現這是難以做到的。因為:
iptables是不支援變數的-這好像是一個老生常談的痛處了。
我希望最終的優化後的規則集按照下面的邏輯執行,以快速定位到Action:

這裡寫圖片描述

這很好,但是做不到,為什麼呢?

  我們先把上面的圖示轉化為iptables規則:

# 首先在Match1的搜尋樹中查詢
iptables -A INPUT --match1 a -g self_define_chain1_1
iptables -A INPUT --match1 b -g self_define_chain1_2

self_define_chain1_1:
...

self_define_chain1_2:
...

self_define_chain_x_end:
...
# 到達葉子節點,跳入Match2的查詢樹
iptables -A self_define_chain_x_end --match1 x -g Match2

# 以下是Match2的查詢
iptables -A Match2 --match2 a_1 -g self_define_chain2_1
iptables -A Match2 --match2 b_1 -g self_define_chain2_2
# 現在問題是,匹配執行到這裡的時候,執行流並不知道自己是從哪裡跳過來的!

...

是的,這就是問題所在,當執行流在Match2的搜尋樹裡搜尋的時候,它已經忘了在Match1搜尋樹裡得到的結果!這就像狗熊掰棒子一樣,最終註定只有最後一維的Match搜尋結果可以保留下來。

  到此為止,該想到的也許你也已經想到了。是的,使用nf_MARK!然而這就是痛點所在,IPMARK是一個常量,而不是變數!比如你無法像下面這樣僅僅通過一條iptables規則就可以通配所有的匹配:

iptables -A XXXXX ! --mark 0 -j --GET_MARK_and_GOTO;

這條規則本意是如果一個數據包的nf_MARK不為0,那麼就跳轉到該資料包的IPMARK指示的chian中。然而這在iptables中是無法做到的!在配置一條規則的時候,你必須已經知道IPMARK是多少。當然,一種不那麼好的解藥是使用xtables-addons中的一個叫做IPMARK的extension,它可以用IP地址生成MARK,有點變數的意思,但是在規則新增的時候,IP地址肯定是無法確定的。

  所以說,我的做法是修改IPMARK extension,使之滿足:

iptables -A XXXXX ! --mark 0 -j --GET_MARK_and_GOTO;

這樣的話,在每goto到一個新的維度進行新的Match搜尋的前,我可以把當前Match搜尋的結果索引放在一個變數中,該變數在資料包未離開Netfilter期間都有效。

  由此,在全部的Matches查詢樹都搜尋完畢的時候,就可以使用已經儲存在核心中的各個Match的區間索引,然後用這些索引來定位Action!有了這個基礎設施的話,預處理後的iptables規則集就可以減少很多了,每一個Match維護一棵查詢樹即可!

如何想出n+1模型

哈,看看這是什麼?這不就是nf-HiPAC嗎?

  本文所說的方法,只是在使用者態對iptables規則集的組織進行了一個重構,就達到了和nf-HiPAC相同的效果,二者的思想非常一致,其實都是一種針對查詢定位演算法的優化,如果我們能在上計算機基礎課學過氣泡排序後或者考試的時候想到用各種重定序代替遍歷,那麼在遇到iptables的時候,為什麼就很少有人能想到呢?!

  也許,當老師逼著你問你如何排序一個序列的時候,你滿腦子想的只有一個結果,那就是“最終我一定要拿出一個比遍歷要好的演算法”,至少你的潛意識裡就會覺得遍歷一定是最Low的。然而,當你遇到iptables的時候,沒人逼你去優化,恰恰相反,iptables在絕大多數的場景下,它工作的非常OK,如果你對此一無所知,那麼另一方面,絕大多數的人是不懂iptables在核心中是如何被執行的,至少大家沒有動力去了解這個,我敢說,很少有人看完過ipt_do_table函式,是吧?

  那麼這重重障礙後,誰還會能有動力去優化iptables的不足,即便是少數人看到了弊端,也只能要麼另起爐灶搞個nf-HiPAC最終卻銷聲匿跡,要麼向iptables內靠,將ipset做成一個隸屬於iptables的模組,很少有人有力量去觸動iptables本身,直到nftables的徹底顛覆。即便是nftables,它的前端目前也僅僅是在相容了iptables的標準用法之外並沒有做太多,雖然nftables的核心支撐結構完全發生了改變,其完全有能力在使用者態做出一個完美的多維樹匹配結構,但卻沒有任何必須這麼做的理由。還是同樣的基因,只是整形了。

  我在本文中描述的預處理,雖然最終還是輸出了iptables規則集,但是卻是完全不同的理念。如果說iptables規則是豎著排列的線性集合,那麼n+1模型的iptables規則則是加入了橫排列的立體分層的集合。即便如此,我不得不說,本文的n+1預處理模型依然只是一次小小的外科手術,然而這符合我的風格,不喜歡重新來過,我比較在行的是,利用所有現有的東西,將它們組合成一個更加精妙的裝置,這裡的東西,並不僅僅指物件,還包括執著勇氣

關於iptables的設計

得不提出的問題終於還是來了,我必須面對且必須要自問自答。

  說白了,設計這個n+1模型的初衷,不還是因為iptables有問題嗎?本文的“動機”一節中,我不是還怒懟那些認為iptables有問題的人,現在自己豈不是自打臉了?
哈哈,我必須做出幾點解釋,首先依然強調iptables並不Low,相反,它很好,認為它Low的是你沒玩好它。其次要解釋的是,iptables為什麼不是從一開始設計的時候就包含我這裡扯的這些亂七八糟的東西,包括n+1預處理模型,既然我這麼Low的人都能想到的這個優化,iptables的作者能想不到嗎?嗨,這個問題只能去問iptables的作者了,質問他為什麼不。

  其實,iptables只是提供了一個介面的底層,該介面蘊含的無限種可能性和玩法需要你必須自己去挖掘,輪子和發動機有人提供,然而車卻需要你自己來造。

  最近我同步在做一個半手工版的步進電機(一個很樸素很Low的設計),依然使用的樹莓派,完成後我舒了一口氣,不再抱怨。

  我曾經抱怨,為什麼樹莓派不自帶幾個例子,為什麼所有的一切都要我自己來做,甚至連個SD卡都不送!是的,樹莓派就是一塊單板,你花200多塊錢買回來後,如果不想繼續花錢買別的東西,你甚至無法點亮一個燈,至少SD卡是沒有的吧。然而當你把需要的配件買回來後,就會發現,無非也就是花了點錢而已,且不需要花很多的錢,這很棒。

  iptables與此類似,單單iptables本身,非常直接,它僅僅是一個支撐設施,然而沒有人會直接住在井蓋上,也沒有人會直接燒石油,所以很多東西確實需要你自己來做。幾年前,當我自己曾經手工新增100多條iptables規則的時候,我意識到,要讓最容易匹配的規則位於規則鏈的相對前面的位置,這樣可以減少開銷。我知道,這是一個很樸素的想法,但卻已經有點我想優化它的慾望,既然我能手工做到規則排序,為什麼不能用程式搞定呢?…

  過了好幾年了,各種想法揉在一起,這便有了n+1預處理模型。

  那麼程式碼呢?

n+1模型的程式碼何在

近實在是不想寫程式碼,一來是因為最近總是加班,根本就沒有時間,二來是因為我想用指令碼語言來實現這個前端而不是用C/C++/Java這種編譯型的語言,然而我對指令碼語言並不是很在行,水平比較垃圾,除非有足夠的外部刺激,我完成這個程式碼還有難度的,考慮到颱風“天鴿”已經不在,颱風“帕卡”也即將成為過往,而且週末加了兩天班,今天週日晚上還有飯局,所有的外部刺激都是反方向的,即越來越不可能完成這個程式碼…就連這篇文章也是花了週五和週六晚上和週日早上的時間寫的…

  所以,程式碼實現只能由溫州皮鞋廠老闆來TODO了。以下是溫州老闆的程式碼片段:

這裡寫圖片描述

敬請期待溫州皮鞋廠老闆的傑作吧!!

  溫州皮鞋廠老闆,何許人也?溫州皮鞋廠老闆是我的一位朋友。溫州老闆是我寫的幾乎任何隨筆中均出現過的福爾摩斯的華生般的神祕人物,當然,我不敢自詡福爾摩斯,至多是柯南·道爾(Arthur Conan Doyle)罷了…

  溫州老闆平時喜歡研究各種程式語言以及各種演算法,且對系統和網路的底層原理有著深刻的理解,從網絡卡虛擬化技術到TCP加速,從分散式儲存到一致性雜湊,都是老闆的領域,老闆而且還對TLS/SSL,Https有一定的研究和自己的認識…同時,溫州老闆是一位吉他手,精通樂理,喜歡複雜的電聲裝置而鄙視一切樸素的樂器,比如口琴,二胡,木吉他這種,老闆也是一位搖滾樂愛好者,有點崇洋(但不媚外)地喜歡國外金屬,而鄙視國內“土搖”(也不知道誰取的這個名字…)。這就是溫州老闆,一位比較有意義的人。推薦溫州老闆的一個Blog:
http://www.xxx.net
【刪除了,老闆比較低調。。。】

尾聲

本文中,我試圖將孤立的iptables規則整合在了一起,形成了一個n+1維度的空間,每一條傳統的iptables規則在該空間中都可以展開成一個n+1維超立方體,最終又把這個n+1超維空間對映到了n棵順序嫁接的二叉查詢樹

  其實,iptables還是很多好玩的小Trick,能玩轉它不光可以讓你在使用它的時候得心應手,更重要的是可以幫助你深入理解iptables的細節和本質,從而消除很多的誤解。

  早在接觸了nf-HiPAC的時候,我看到了多維區間樹的精妙之處,後來KD-Tree,DxR,DxRPro++等讓我的這種認識逐漸深化,其實這多維樹是一個非常簡單的思想,它可以構建出很多高效且好玩的東西,路由查詢,包分類,防火牆這些只是其中比較直接的,其它的還包括Bloom Filter,傅立葉級數的理解,無理數的理解等等…

  如果希望進一步地理解區間樹更加嚴謹的數學描述,請參考本文“已有的優化方案”一小節中的“補遺”中引用的資源。

  這是2017年8月份的最後一個週末,下週小小就要開學了,正式開始小學之路,希望小小也能像我一樣,勤於總結,勤於分享,每天進步!

補遺

雖然我知道已經有了我本文中闡述的方案,並且也有數學邏輯分析,瞬時感覺自己慢了不止一步。在以往的十年,自詡在iptables領域捨我其誰,然而碰到硬茬卻是無可奈何,不過我還是有話要說。
  雖然CF專案可以實現區間匹配,但是,它實現得太複雜了,它的熱度並不高,幾乎沒有關注。所以現如今需要一個簡版的,指令碼實現的,可操作性強的,這是必要的。畢竟…
  畢竟真的有人在不想改任何他的iptables規則的情況下,讓我為他優化iptables防火牆效能….