玩Linux網絡的同好一定希望可以任意定義網絡處理邏輯的行為,可謂協定處理的高端定制,最顯而易見的辦法就是在結構體裡面加一個字段,事實上Linux的一個入口流控更新檔IMQ就是這麼做的,它簡單的修改了Linux核心的sk_buff結構體的定義,增加了一個字段,增加了一個IMQ使用的字段,然後重新編譯了核心...
通過重新編譯核心,總是能滿足任何的需求,但是噩夢本身就是重新編譯核心!我特别讨厭重新編譯核心,因為它極其浪費寶貴且有限的時間,另外這種做法太不即插即用,場面太宏大。Linux核心代碼什麼時候能像面向對象語言那樣可以任意擴充呢?是的,LKM可以,但是網絡協定棧作為一個核心功能并不是通過LKM實作的,是以除了重新編譯還是重新編譯。
近期買了一本《JAVA程式設計思想》,一直糾結于這本書到底展現了什麼思想,看得多了,便有了一點想法,對象,對象的思想是這本書的核心,注意,不是多态,而是對象本身。而對象的好處在于其可擴充,完全的可擴充。是以循着這個思想,我想做一個一勞永逸的工作,讓sk_buff可擴充。
Linux核心時時刻刻考慮了結構體的擴充,是以許多的結構體都包含一個叫做private_data的字段,也可能叫做private或者priv等。事實上,幾乎所有的C語言寫就的架構都采用了類似的思想,比如file結構體:
更一般的,該字段作為結構體的最後一個字段存在,這已經很接近OO的思想了,但是卻永遠無法達到,因為,一個private字段是作為結構體内部的擴充,即對象的屬性擴充,而不是類型本身的擴充。怎麼說呢?如果我建立了一個對象,即:
看看我是對誰賦的值,是對f指派,f是一個對象,而不是類型,在這個意義上,類型是什麼?是struct file,我需要的是對file結構體的擴充。按照OO的思想,我應該這麼做:
這麼做和直接在file加入private_data相比,更能展現OO的思想。如果直接在file結構體加入private字段,那麼任何人都可以對任何file執行個體的private字段進行替換和解釋,隻要它拿到file執行個體即可,但是如果擴充的是struct file類型,那麼隻有類型的定義者和了解類型定義的人才能對其進行操作。舉例如下。如果直接在file結構體加入private,那麼下面的指派就是合法的:
這樣一來,除非頻繁加鎖,否則一個file的擴充就是不穩定的,它可以在任何地點被重定義。反之,如果是對struct file的定義進行擴充的話,那麼
和
就是兩個類型,任何時候,通過txtfile1的對象引用private2字段都是非法的,當然上述情況下你完全可以通過強制類型轉換達到目的,但是請注意,C語言本身對類型檢查就不嚴格。你也可以通過記憶體操作來達到任何不可告人的目的,但是你要知道,計算機世界的任何事情都可以通過記憶體操作來進行,如果你為了炫技巧非要采用那麼原始的操作,那也無妨。
了解了思想以後,我們就可以着手修改代碼了,很簡單,為了更加通用,我們隻是希望擴充一個指針,指針真乃C語言的一項藝術,它可以将一維的記憶體擴充到多元,它可以指向任何東西。然而在配置設定skb的時候,并不知道該指針指向哪裡,也不知道它的具體類型,是以隻需要預先配置設定好記憶體即可,在記憶體不值錢的今天,多個幾個位元組又何妨(很多弊病都是從資源匮乏階段繼承下來的,比如勤儉節約),即:
這樣你就可以在其它代碼不知情的情況下擴充skb了,比如定義以下結構體:
在PREROUTING的地方調用:
然後可以在任何後續的地方将info取出來。但是這有一個問題,那就是如果出現第二個執行緒,它完全可以将info的指派修改掉,即便它不明白結構體data_extends_skb的定義,也不知道info字段的具體名字,它如果這麼做:
也會取消掉代碼作者的本意。當然,這在C語言中是沒有辦法的。
Linux協定棧傳遞skb的不合理性
Linux并不是完全都是合理的,它也有不合理的地方,如果想用OO的思想重構協定棧實作,那麼它現有的架構将是很不合适的:
1.skb在配置設定以後就不能再次被配置設定重新定義
skb在資料包進入協定棧後隻配置設定一次,從此以後直到它離開協定棧,僅僅靠移動它的資料指針來訓示現在到了哪一層,在任何層的處理函數中,skb的結構體本身無法改變。這種想法實際上最初是為了效率而引入的,如果你看過《TCP/IP詳解(第二卷 實作)》,你就會知道之前的UNIX mbuf完全不是用的這種方式,事實上,mbuf機制在每一層都要經過一次重定義,這顯得效率很低,但是今天返璞歸真的話,它正是展現了OO的思想。
Linux的skb隻配置設定一次意味着你隻能基于skb資料的訓示路由skb,或者修改skb内部的字段的值,但是卻不能改變skb本身,即你不能将這個現在的skb釋放掉,然後再配置設定一個新的skb代替它,或者将老的skb的内容複制到新的skb中,然後在新的skb中加入新的東西。
2.Linux的Netfilter架構完全繼承了Linux協定棧的處理方式
我們知道,NF_HOOK傳回值就是協定棧的傳回值,即一個int型的值,表示成功或者失敗。但是何必呢?
完全可以定義成:
如此一來,就可以在Netfilter中實作skb的重定義(我還沒有勇氣對整個協定棧開刀,要改的地方太多太多了,還是隻對Netfilter開刀吧),這是意義非凡的,迎合了OO思想的向上轉型,即任何一個特殊的類的對象都可以轉型為更加一般的其基類對象。看似操作的是sk_buff對象,實際上它隻是一個基類對象。在Netfilter的hook中,你完全可以這樣:
隻要可以修改skb的指針了,那就意味着可以重新定義skb的類型了,這也意味着擴充skb成了可能。 需要注意的是,等到需要确定一個skb到底是什麼類型的時候,怎麼辦呢?OO語言内置的有類型檢查,反射等機制,比如JAVA的instance of宏等,可在Linux核心這種底層C寫就的代碼中,如何來做呢?C語言沒有進階OO語言的種種内置特性,這完全需要程式員自己來解決這個問題,比如你不能對一個原生的skb取v2的值,而這個是不能保證的,正如你可以寫下以下的代碼一樣:
越是進階的語言總是讓人不關注語言以及計算機機制本身,而專注于自己的業務邏輯,但是以計算機為本業的系統程式員,或者網絡安全系統程式員,卻最好不要去用什麼進階的語言,因為你的業務邏輯就是要搞定計算機或者網絡所呈現的缤紛複雜,而不是和一群穿西服的人論戰...這也是一個思想!
本文轉自 dog250 51CTO部落格,原文連結:http://blog.51cto.com/dog250/1394884