(三)洞悉linux下的Netfilter&iptables:內核中的rule,match和target

作爲ipchains的後繼者,iptables具有更加優越的特性,良好的可擴展功能、更高的安全性以及更加緊湊、工整、規範的代碼風格。

    在2.6的內核中默認維護了三張表(其實是四張,還有一個名爲raw的表很少被用到,這裏不對其進行分析介紹了):filter過濾表,nat地址轉換表和mangle數據包修改表,每張表各司其職。

我們對這三張表做一下簡要說明:

    1)、filter表

    該表是整個過濾系統中真正起“過濾”作用的地方。所有對數據包的過濾工作都在這個表裏進行,也就是說用戶如果需要對某種類型的數據包進行過濾攔截,那麼最好在這個表中進行操作。filter表會在NF_IP_LOCAL_IN、NF_IP_FORWARD和NF_IP_LOCAL_OUT三個hook點註冊鉤子函數,也就是說所有配置到filer表中的規則只可能在這三個過濾點上進行設置。

    2)、nat表

    主要用於DNAT和SNAT和地址僞裝等操作。用於修改數據包的源、目的地址。目前版本的內核中nat表監視四個hook點:NF_IP_PRE_ROUTING、NF_IP_LOCAL_IN/OUT、NF_IP_POST_ROUTING。但在真正的實際應用中,我們一般僅需要在nat表的PREROUTING和POSTROUTING點上註冊鉤子函數。該表有個特性:只有新連接的第一個數據包會經過這個表,隨後該連接的所有數據包將按照第一個數據包的處理動作做同樣的操作,這種特性是由連接跟蹤機制來實現的。

    3)、mangle表

    該表主要用於對數據包的修改,諸如修改數據包的TOS、TTL等字段。同時該表還會對數據包打上一些特殊的標籤以便結合TC等工具,實現諸如Qos等功能。該表監視所有的hook點。
 
    IT界有位大牛(具體是哪個我記不太清楚了)曾給程序下的定義是:程序=數據結構+算法。可見數據結構在整個程序設計過程中的重要性了。我本人也比較贊同這種說法。我們今天主要探究一下通過用戶空間的iptables所配置到內核中的每條規則到底是個啥樣子。

   在 Netfilter 中規則是順序存儲的,一條rule規則主要包括三個部分:

 

  • ipt_entry:標準匹配結構,主要包含數據包的源、目的IP,出、入接口和掩碼等;
  • ipt_entry_match:擴展匹配。一條rule規則可能有零個或多個ipt_entry_match結構;
  • ipt_entry_target:一條rule規則有且僅有一個target動作。就是當所有的標準匹配和擴展匹配都符合之後纔來執行該target。

 

   結構體struct ipt_entry{}的定義在include/linux/netfilter_ipv4/ip_tables.h文件裏。其結構圖如下:

上面這幾個結構體的成員屬性基本上已經做到了“見名知意”,而且內核源碼也對它們做了充分的註解。這裏只對最後一個屬性elem做一下說明,其定義爲unsigned char elems[0]。大家可能覺得有些奇怪,怎麼定了一個大小爲零的數組呢?而且有些面試官曾經就這樣的定義還向面試者發問過呢。這種方式定義的數組叫柔性數組,又叫可變長數組。爲了不至於沖淡本文主題,這裏給出一個關於柔性數組的鏈接,這位大牛已經將的很清楚了,大家可以去拜讀拜讀:http://blog.csdn.net/supermegaboy/article/details/4854939。更多詳細的內容可以去研讀C99標準。

       我們將看到內核中大量的在運用柔性數組,包括我們即將要介紹的這兩個結構體:

    這兩個雙胞胎兄弟一眼望去還以爲它們是同一個東西,但事實並非如你所想的那樣。其中ipt_entry_match{}表示防火牆規則的匹配部分;ipt_entry_target{}表示防火牆規則的動作處理部分。大家先忽略掉它們左邊那條煩人的提示部分union,後面我會詳細介紹的,現在請跟我一樣盡情地無視它們吧。

    這裏我們還注意到,這兩個傢伙分別都拖了一條小尾巴:ipt_match{}和ipt_target{}。對於前面我們提到過的標準匹配,它只會去檢查數據包的IP地址,源目的接口,掩碼等通用信息,不會用到ipt_entry_match{}。對於以模塊形式存在的擴展匹配,如iprange模塊,ipp2p模塊等,它們就得實現自己的ipt_match{}結構。也就是說,如果你要開發一個新的match模塊,那麼就必須去實例化一個ipt_match{}結構體對象,並實現該結構體中相應的成員屬性(其實主要都是一些些函數指針成員,你必須實現相應的函數體),然後將該ipt_match{}對象掛在你的ipt_entry_match{}結構的match屬性裏就OK了,就這麼簡單。

    同樣的,我們來說一下ipt_target{}結構體。對於target(我這裏就不翻譯了,那個叫“動作”的翻譯太難聽了,後面我都用英文表述)也分爲標準target和擴展target。標準target就是那些ACCEPT、DROP、REJECT等等之類的處理方式;擴展target就是那些諸如DNAT、SNAT等以模塊形式存在的target了。對於標準的target,它是不需要ipt_target{}結構的,即ipt_entry_target{}中的target屬性爲NULL;而對於我們自己擴展target是需要我們自己手工去實現ipt_target{}對象,並完成相關回調函數的編寫。對於ipt_target{}結構體中target回調函數的編寫有一點要注意:該函數必須向Netfilter框架返回IPT_CONTINUE、或者諸如NF_ACCEPTNF_DROP之類的值。開發細節我會在後續動手實踐章節一一向大家說明。

    結構體ipt_entry_match{}定義在include/linux/netfilter/x_tables.h文件中。

    結構體ipt_entry_target{}也定義在include/linux/netfilter/x_tables.h文件中。

    結構體ipt_match{}定義在include/linux/netfilter/ip_tables.h文件中。

    結構體ipt_target{}也定義在include/linux/netfilter/ip_tables.h文件中。
 

匹配match:

    上面我們說過,match分爲兩種:基本match,又叫標準match;擴展match。

n  標準match:

標準匹配主要用於匹配由struct ipt_ip{}所定義的數據包的特徵項。標準匹配的內核數據結構就是我們上面所看到的ipt_match{}定義在include/linux/netfilter/ip_tables.h。在所有的表中我們最後真正所用到的match結構爲ipt_entry_match{},它和ipt_match{}的關係我也將其畫出來了,如上所示。

既然說到這裏,那我就再囉嗦一點。對於ipt_entry_match{}的結構大家可能也留意到了它內部結構有個union成員,同時它還區分了user和kernel兩種情況。我們剛剛在上面所討論的ipt_match{}結構是內核中用來表示match的數據類型,在用戶空間我們用的是iptables_match{}結構(定義在iptables.tar.gz源碼包中的include/iptables.h頭文件中)來表示match的。

也就是說,內核空間和用戶空間在註冊和維護match時使用的是各自的match結構,ipt_match{}和iptables_match{},但是當某個具體的match被應用到防火牆規則裏時,它們兩個必須統一成ipt_entry_match{},這纔是防火牆規則中真正用到的match結構。

至於iptables_match{}和ipt_match{}是如何完美地統一到ipt_entry_match{}結構中的,我們在後面再來詳細分析。

n  擴展match:

擴展match通常以插件或模塊的形式存在。當我們在用戶空間通過iptables命令設置規則時如果用到了-m  ‘name’ 參數時,那麼此時‘name就是一個擴展匹配模塊。前面我們也說過,如果你需要開發一個新的match模塊,那麼就必須去實例化一個ipt_match{}結構體對象,並實現其中的重要回調函數(如match()函數),最後通過xt_register_match()接口將你的ipt_match{}對象註冊到Netfilter中去就可以了。實戰篇我們講解如何開發一個新match的全過程。

 

動作target:

         根據上面的兩幅圖我們可以看到ipt_entry_match{}和ipt_entry_target{}的結構基本如出一轍,那麼它們也就存在着很多非常相似的地方了。在所有的表中關於target的使用,我們既可以用ipt_standard_target{}又可以用ipt_entry_target{},這是爲什麼呢?後面再解釋。這兩個結構體的關係如下圖所示:

    怎麼樣很簡單吧,就多了一個verdict變量而已。說了半天,那麼target到底是用來幹什麼的呢?說白了,target主要用來處理:當某條規則中的所有match都被數據包匹配後該執行什麼樣的動作來處理這個報文,最後將處理後結果通過verdict值返回給Netfilter框架

    同樣的target也分內核空間和用戶空間兩種結構。在內核空間中,我們所說的target由ipt_target{}表示,定義在include/linux/netfilter/ip_tables.h文件中;在用戶空間中,所使用的是iptables_target{}結構,該結構定義在iptables源碼包裏的iptables.h頭文件中。同樣地,ipt_target{}iptables_target{}最後也完美地統一到了ipt_entry_target{}裏。

iptables的-j參數後面即可跟諸如ACCEPT、DROP這些動作,也可以跟一條用戶自定義鏈表的名字。我們都知道iptables中的規則鏈(chain)其實就是某個hook點上所有規則的集合。除了系統內建的鏈外,用戶還可以創建自定義的新鏈。在iptables中,同一個鏈裏的規則是順序存放的。內建鏈的最後一條規則的target是鏈的policy策略,而用戶自定義鏈中是沒有policy這麼一說。用戶自定義鏈的最後一條規則的target是NF_RETURN,遍歷過程將返回原來的鏈中。當然,規則中的target也可以指定跳轉到某個用戶創建的自定義鏈上,這時這條規則的target就是ipt_standard_target{}類型,並且這個target的verdict值大於0。如果在用戶自定義鏈上沒有找到任何匹配的規則的話,遍歷過程將返回到原來調用這條用戶自定鏈的鏈裏去匹配下一條規則。

這裏還需要注意一點:target也分爲標準target擴展target,前面簡單提過一些。它和標準的match以及擴展match還是有些區別:

標準的target,即ipt_standard_target{}裏可以根據verdict的值再劃分爲內建的動作或者跳轉到自定義鏈中。簡單了說,標準的target就是內核內建的一些處理動作或其延伸。

擴展的target,則完全是由用戶定義的處理動作。如果ipt_target.target()函數是空的,那就是標準target,因爲它不需要用戶再去提供新的target函數了;反之,如果有target函數那就是擴展的target。

    如果我們要開發自己的target,那麼也只需要實例化一個ipt_target{}對象,並填充其內部相關的回調函數,然後調用xt_register_target()將其註冊到Netfilter框架即可。

 

規則rule:

         其實每張table表中最後真正用於表示其內部所有規則的結構體是ipt_standard{},定義在include/linux/netfilter_ipv4/ip_tables.h文件中。最後在我們幾張表裏,如filter表,nat表裏真正用的規則結構爲ipt_standard{}。它和我們前面介紹的ipt_entry{}的關係如下:
    未完,待續...
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章