版權聲明:本文為博主原創(chuàng ),無(wú)版權,未經(jīng)博主允許可以隨意轉載,無(wú)需注明出處,隨意修改或保持可作為原創(chuàng )!
這不是一篇教你怎么可以配置nftables實(shí)現一個(gè)哪怕最簡(jiǎn)單防火墻的文章,我從來(lái)不寫(xiě)這種Howto,因為我覺(jué)得如果一項新技術(shù),一個(gè)人連其本身的文檔都懶得看,即便沒(méi)有文檔如果沒(méi)有一點(diǎn)鉆研精神將其搞懂,只靠看別人寫(xiě)好的Step by step的話(huà),那真是太失敗了。相反,這篇文章是一篇檄文,只為吹擂打鼓,目的是讓你在無(wú)感于iptables的前提下愛(ài)上nftables。
---------------------------------
關(guān)于nftables,請參見(jiàn)以下幾篇文字:
1.同樣一篇檄文,翻譯過(guò)來(lái)的,它是《Linux 首次引入 nftables,你可能會(huì )喜歡 nftables 的理由》
2.nftables的文檔,這個(gè)最具有權威性,它是《Nftables HOWTO 中文翻譯》,我跟朋友說(shuō),這個(gè)文檔就像當年的iptables文檔一樣好,我給出個(gè)中文鏈接,喜歡英文的請自行搜索。
3.詳細剖析nftables語(yǔ)法以及內部結構的一篇文章,它是《What comes after 'iptables'? Its successor, of course: `nftables`》,很遺憾它沒(méi)有中文翻譯,不過(guò)看我的應該就夠了。
4.我自己在2014年的時(shí)候寫(xiě)的一篇關(guān)于nftables的文章,它是《繼iptables之后的新一代包過(guò)濾框架是nftables》,說(shuō)實(shí)話(huà),我確實(shí)自己又看了一遍這個(gè)。如果你真的完整看過(guò)了以上4篇,我相信下面的文字是很輕松的。而且,你在讀完下面文字的時(shí)候,應該會(huì )形成一種形而上的觀(guān)點(diǎn)與我展開(kāi)討論,熱烈歡迎!
---------------------------------
要站在歷史的延長(cháng)線(xiàn)上觀(guān)看nftables,它是第X代Linux防火墻。它的前驅是iptables,再往前是ipchains,ipfirewall等等...如果你仔細觀(guān)察它們的名字,就會(huì )發(fā)現,nftables中完全用nf前綴取代了ip前綴,這是不是意味著(zhù),之前類(lèi)似iptables,ip6tables,arptables,ebtables等等“工具族”,完全被統一在nf-框架之上了呢?答案無(wú)疑是肯定的,nftables完全統一了所有這些,正如其名字所示,nf代表Netfilter,而不管是ipchains,iptables,ebtables,arptables,底層所依賴(lài)的機制完全就是Netfilter。nftables統一了所有這一切!伴隨著(zhù)這個(gè)事實(shí),理所當然會(huì )有下面的疑問(wèn):
nftables的外部語(yǔ)法和內部架構該如何變化以適應這個(gè)大一統的名字呢?
我們從名字開(kāi)始,逐步開(kāi)始向下挖。
---------------------------------
首先我們先從離我們最近的地方,即用戶(hù)接口看起。這部分側重表現在命令或者說(shuō)語(yǔ)法方面。請注意,iptables的規則是沒(méi)有語(yǔ)法的,它只是一系列命令行參數的組合,而nftables的語(yǔ)法也并不是完善的,起碼現在還很弱,不過(guò)我們要向前看有所期待。
有文章說(shuō)nftables是借鑒了tcpdump的語(yǔ)法,將-a $a或者--abc $abc這種語(yǔ)法改成了類(lèi)似a $a之類(lèi)的語(yǔ)法,我不太認同nftables是借鑒了tcpdump,因為這是nftables自然而然的做法。
如果是iptables時(shí)代,我們使用下面的規則:
iptables -A OUTPUT -p tcp --dport 80 -j DROP到了nftables時(shí)代,同樣的規則,會(huì )變成下面的樣子:
nft add rule ip filter output tcp dport 80 drop雖然長(cháng)了些,但你會(huì )發(fā)現,所有的字詞都完整地構成了一個(gè)個(gè)的句子構成元素,比如主語(yǔ),謂語(yǔ),賓語(yǔ),狀語(yǔ),定語(yǔ)等,然后回看iptables的命令行參數,幾乎全部都是限定性的,比如出現-p,就意味著(zhù)后面將要出現的就是協(xié)議...在nftables中,沒(méi)有這種限制了。映射到程序中,iptables可以用getopt這種方式處理“命令行參數”,請注意,iptables將所有的子命令都作為了命令行參數來(lái)看待,而nftables則完全不同,它處理子命令的過(guò)程完全是語(yǔ)法分析和詞法分析的過(guò)程,所有你敲打進(jìn)去的每一個(gè)單詞都將構成一顆樹(shù)上的節點(diǎn),nftables是有“真正的語(yǔ)法”的,所以說(shuō),你配置nftables規則的過(guò)程,就是一個(gè)編程的過(guò)程。編程的過(guò)程是一個(gè)直觀(guān)且簡(jiǎn)潔的過(guò)程,就像人們自然而然的寫(xiě)下一個(gè)句子一樣。
外部語(yǔ)法的升華背后,那是內部結構的調整。
---------------------------------
nftables的內部結構與iptables相比,有哪些變化呢?
首先,我們先看下規則的布局。
我們知道,iptables規則的布局是基于連續的大塊內存的,我之前曾經(jīng)稱(chēng)之為數組式布局,寫(xiě)到這里,我想你應該知道我想說(shuō)什么了,既然iptables規則是數組式布局,那么nftables規則會(huì )不會(huì )是鏈表式布局呢?答案無(wú)疑是肯定的。那么iptables規則的布局和nftables規則的布局之間的區別,其實(shí)就是數組和鏈表的區別了。
舉個(gè)最簡(jiǎn)單的例子,數組的元素要是被刪除了,保證順序不變的前提下,后面所有的元素都要往前移動(dòng),試想10000個(gè)元素的數組刪除array[1]的情況,鏈表就不存在這個(gè)問(wèn)題,只需要修改一個(gè)或者幾個(gè)指針即可。當然,數組還是有好處的,內存緊湊,cache命中率高,可以索引定位什么的...
當然,我并不曉得iptables當初為什么要采用這種數組式的布局,這很值得深挖一下,但卻不是本文的主題。離開(kāi)這個(gè)話(huà)題前,我必須要說(shuō)的是,對于我個(gè)人來(lái)說(shuō),在快速實(shí)現一個(gè)可以測試的功能的編程過(guò)程中,如果需要容器,我首選的就是數組,因為在實(shí)驗階段它更直接,且不會(huì )遇到O(n)問(wèn)題...最后等真的遇到問(wèn)題且有替換數組為鏈表的十足理由了,我才會(huì )去重構成鏈表式的實(shí)現,否則它就在那里了。
看完了規則的布局差異,我們繼續往下看。
如果你熟悉iptables的內核機制(如果不知道,請Stop,本文不是介紹這方面的Howto),你就會(huì )知道它實(shí)際上是一個(gè)非常規則且簡(jiǎn)單的“機械裝置”,這套裝置明令要求所有的規則必須由若干的matches和一個(gè)target組成,我們經(jīng)常在肯德基或者老式工廠(chǎng)(比如老式機床廠(chǎng))的車(chē)間見(jiàn)到類(lèi)似的這種裝置。這類(lèi)裝置的特點(diǎn)是“工序是固定的”!我以煎蛋為例,如果我想煎三個(gè)雞蛋,并且我有一把足夠大的平底鍋,我會(huì )一次性把三個(gè)雞蛋全部打在刷了足夠植物油的鍋底,這也是一種自然而然的做法,然而如果用類(lèi)似iptables內核機制那樣的煎鍋,不管鍋有多大,每次只能煎一個(gè)雞蛋,如果你想煎三個(gè)雞蛋,很簡(jiǎn)單,重復三次且只能重復三次。這種裝置是“不可編程的”!
我們生活的世界不是這樣子的!試想你去一趟超市只能買(mǎi)一樣東西,并且不能再去別的地方,現在需要你買(mǎi)來(lái)做午飯的所有食材,鑒于超市的蔬菜可能不太新鮮,你要去路對面的蔬菜店單獨購買(mǎi)蔬菜,怎么辦?使用iptables裝置是“可以實(shí)現的”,但也僅僅是可以實(shí)現而已,除非我們是機器人,否則這種世界是不令人舒適的!我們更容易適應的世界應該是這樣子:大早上起床先去海鮮市場(chǎng)買(mǎi)條魚(yú),然后拎著(zhù)魚(yú)去蔬菜店買(mǎi)蔬菜,然后拎著(zhù)魚(yú)和蔬菜寄存在超市,進(jìn)入超市購買(mǎi)各種肉類(lèi),酒水飲料以及家里需要的日用品,結完帳取出寄存的魚(yú)和蔬菜,拎回家或者一起放入汽車(chē)的后備箱。
幸運的是,這就是nftables的方式!
nftables不再固定“機械裝置的行為”,行為完全由你自己來(lái)確定,它只是實(shí)現了幾個(gè)足夠獨立的動(dòng)作,組合這些動(dòng)作你幾乎可以實(shí)現任何操作,事實(shí)上,這種新的裝置解構了行為,將行為分解成了更加基礎的動(dòng)作,你讓它執行什么動(dòng)作它就執行什么動(dòng)作,怎么組合這些動(dòng)作以及最終完成什么樣的行為,全在你。這裝置是什么?這好像跟電腦很像。其實(shí)跟我們人本身更像!稍微術(shù)語(yǔ)一點(diǎn),這就是一部虛擬機。nftables竟然在Linux內核協(xié)議棧里實(shí)現了一個(gè)虛擬機!其實(shí)不止一個(gè),而是5個(gè)!Netfilter的5個(gè)HOOK點(diǎn)上均有一部這樣的虛擬機。
和以往固定的機械裝置不同,你灌輸給虛擬機的不光是數據,還有程序,而固定的機械裝置只能灌輸給其數據,因為程序是固定的。
回過(guò)頭來(lái)我們再來(lái)看一下nftables的語(yǔ)法特點(diǎn),一條規則其實(shí)就是一個(gè)程序,一個(gè)簡(jiǎn)單的程序就是一個(gè)描述性的祈使句,誰(shuí),在什么地點(diǎn),什么時(shí)候,用什么,做什么事:
nft add rule ip filter output tcp dport 80 drop如果想看個(gè)究竟,那么加上
--debug=netlink,我們看下其輸出:
ip filter output
[ payload load 1b @ network header + 9 => reg 1 ]
[ cmp eq reg 1 0x00000006 ]
[ payload load 2b @ transport header + 2 => reg 1 ]
[ cmp eq reg 1 0x00005000 ]
[ immediate reg 0 drop ]非常明確的一個(gè)程序,簡(jiǎn)直就是另一個(gè)Arch上的匯編程序代碼,用人話(huà)描述這個(gè)程序,就是:
從數據包IP頭開(kāi)始算,取出數據包的第10個(gè)字節開(kāi)始的1字節并把它放入reg 1,比較reg 1的值是不是0x06,如果是的話(huà),以數據包TCP頭開(kāi)始算,取出第3個(gè)字節開(kāi)始的2個(gè)字節放入reg 1,看看它是不是80,如果是,就指示這個(gè)數據包應該丟掉。關(guān)于這個(gè)論點(diǎn),我覺(jué)得再繼續說(shuō)下去就有點(diǎn)拖沓了,用一下nftables便知,如果不知,看下其內核代碼,比iptables的要簡(jiǎn)單。
現在,說(shuō)點(diǎn)關(guān)于人的。
---------------------------------
程序員一般都不懂怎么配iptables規則,甚至都不懂網(wǎng)絡(luò ),而會(huì )配iptables規則的或者會(huì )配網(wǎng)絡(luò )的人一般也不會(huì )編程,他們可能是運維管理員,我一直都徘徊在兩個(gè)陣營(yíng)之間,最終我自己就死皮賴(lài)臉刀槍不入了。我就是個(gè)墻頭草,跟程序員在一起的時(shí)候,說(shuō)那幫運維只懂搬機器,只會(huì )鍵盤(pán)上敲命令,根本就無(wú)力修改命令的行為,而編程才是最高貴的隨心所欲。然而跟運維在一起的時(shí)候,我會(huì )說(shuō),編程者只會(huì )寫(xiě)代碼,以為網(wǎng)絡(luò )就是Socket,充其量就是TCP,根本就不懂IP路由協(xié)議,更別提硬件特征比如網(wǎng)卡指示燈,電源了,出了問(wèn)題他們根本就無(wú)力定位,只會(huì )擼代碼。....雖然說(shuō)這種自嘲其實(shí)是一種自我吹捧,以顯得自己什么都會(huì ),但如果大家都用nftables的話(huà),我便無(wú)力吐槽了。
nftables的運維者成了編程者。
因為nftables規則的編寫(xiě)過(guò)程就是一個(gè)編程的過(guò)程,你可以對不服者如下吶喊:
匯編語(yǔ)言和C語(yǔ)言寫(xiě)的程序喂的是CPU這個(gè)虛擬機;
Java寫(xiě)的程序喂的是JVM這個(gè)Java虛擬機;
nftables編寫(xiě)的規則喂的是Netfilter鉤子點(diǎn)上內置的虛擬機。只要想象一下就興奮,Netfilter鉤子點(diǎn)竟然可以?xún)戎锰摂M機了,那么如此一來(lái),所有的完成特定功能的Netfilter內核模塊都可以扔掉了,在用戶(hù)態(tài)寫(xiě)nftables規則即可。這個(gè)特性
可以吸引iptables使用者去使用nftables,因為后者可以“隨心所有編程”了!我個(gè)人也是比較期待nftables在BSD的Netgraph上有所為的。
但是如此的想法后只能呵呵,其實(shí)nftables還遠遠沒(méi)有進(jìn)化到如此地步。不過(guò)基本思想在,我們可以期待nftables的進(jìn)化。
以前,你是拿著(zhù)原材料到傳統的代工廠(chǎng)生產(chǎn)了一條規則,如今你是用自家的家伙自己“烹飪”了一條規則。
nftables引入了運算符,變量和數據結構,這意味著(zhù)nftables規則的設置者從此成了“編程者”,內核僅僅執行nftables管理員編制的程序,不再做任何假定。
接近尾聲,關(guān)于性能相關(guān)的話(huà)題,我再辯護幾句。
---------------------------------
iptables曾經(jīng)飽受詬病,iptables和Netfilter只能為此承擔了責任。因為iptables和其依賴(lài)的Netfilter內核模塊為用戶(hù)做的太多了!我們來(lái)看看這是為什么。
iptables規則集由N條規則組成,這些規則和Netfilter的5和HOOK點(diǎn)相關(guān)聯(lián),在每一個(gè)HOOK點(diǎn)上,進(jìn)入的IP數據報文要遍歷所有關(guān)聯(lián)與此點(diǎn)的規則,直到某條規則明確返回ACCEPT或者DROP之類(lèi)。如果有10000條規則,那就要最多去匹配10000次。除此之外,進(jìn)入內部,我剛才說(shuō)過(guò),iptables的內核設施明令規定了規則的結構以及執行過(guò)程,寫(xiě)規則的“運維人員”根本就無(wú)力像“程序員”那般去修改其行為以便優(yōu)化,一切都是固定的,如果一個(gè)iptables內核模塊實(shí)現的不好,比如數據結構組織的不好,那么iptables規則的編寫(xiě)者只能興嘆。
正是因為這種無(wú)力,除非必須要用,人們一提起Netfilter/iptables就想到它會(huì )影響性能,這其實(shí)不是Netfilter的問(wèn)題,這是iptables的問(wèn)題,nf-HiPAC也是基于Netfilter的,但為什么就不影響性能呢?
nftables作為iptables的后繼者,擺脫了本不該自己承擔的責任。
從此以后隨著(zhù)nftables版本的進(jìn)化,它的行為和性能只與“nftables編程者”有關(guān),你再也不能抱怨nftables的效率低下了,如果它的性能低下,那只能怪你編程編的不好。nftables作為擁有自己獨立語(yǔ)法的“一種編程語(yǔ)言”,和C語(yǔ)言是類(lèi)似的,如果你的C語(yǔ)言代碼性能很低,你會(huì )怪C語(yǔ)言本身嗎?不光如此,你不能怪C語(yǔ)言,你也不能怪CPU,所以你不能怪nftables,也不能怪Netfilter。
包分類(lèi)這個(gè)性能攸關(guān)的話(huà)題一直以來(lái)真的是性能攸關(guān),一般情況下,人們不敢用iptables來(lái)搞包分類(lèi),這也是事實(shí)。后來(lái)出現了nf-HiPAC,這玩意兒性能非常高,但是其背后卻是復雜的內核代碼,伴隨著(zhù)nf-HiPAC作者被招安以及其作品的商業(yè)化,你能看到的那個(gè)性能比iptables高很多的nf-HiPAC版本其實(shí)只是個(gè)低級版本。使用nftables的話(huà),你甚至可以“烹飪”出一條基于多維樹(shù)匹配的規則來(lái),在iptables中,你無(wú)力放棄線(xiàn)性的逐條matches匹配,但是在nftables中,你卻有能力將其由線(xiàn)性匹配優(yōu)化成一顆多維匹配樹(shù)。
---------------------------------
如果你能看到這里,那么下面的內容也就不重要了。但是雖不重要有可能還是比較有用的。最后來(lái)點(diǎn)Howto。
和xtables-addons一樣,安裝nftables需要先安裝以下的包:libmnl-1.0.4.tar.bz2,libnftnl-1.0.6.tar.bz2,readline-6.3.tar.gz,gmp-6.1.2.tar.bz2。這些都很容易找到,我就不給鏈接了。然后就可以編譯nftables了。內核方面的升級并沒(méi)有難度,我在CentOS 6.7(內核版本為2.6.32)上成功編譯了4.9版本內核(將關(guān)于nftables的編譯選項全部打開(kāi))并安裝,隨后成功安裝了nftable并順利運行,難度并不大。