天天看點

(二)洞悉linux下的Netfilter&iptables:核心中的ip_tables小觑

Netfilter架構為核心子產品參與IP層資料包處理提供了很大的友善,核心的防火牆子產品(ip_tables)正是通過把自己所編寫的一些鈎子函數注冊到Netfilter所監控的五個關鍵點(NF_IP_PRE_ROUTING,

NF_IP_LOCAL_IN,NF_IP_FORWARD, NF_IP_LOCAL_OUT,NF_IP_POST_ROUTING) 這種方式介入到對資料包的處理。這些鈎子函數功能非常強大,按功能可分為四大類:連接配接跟蹤、資料包的過濾、網絡位址轉換 (NAT) 和資料包的修改。它們之間的關系,以及和 Netfilter 、 ip_tables 難舍難分的纏綿可以用下圖來表示:

(二)洞悉linux下的Netfilter&iptables:核心中的ip_tables小觑

從上圖我們可以看出,ip_tables子產品它是防火牆的核心子產品,負責維護防火牆的規則表,通過這些規則,實作防火牆的核心功能。歸納起來,主要有三種功能:包過濾(filter)、NAT以及包處理(mangle)。同進該子產品留有與使用者空間通訊的接口。如第一篇博文中Netfilter處于核心中位置那副圖所描述的情形。

在核心中我們習慣将上述的filter,nat和mangle等稱之為子產品。連接配接跟蹤conntrack有些特殊,它是NAT子產品和狀态防火牆的功能基礎,其實作機制我們也會在後面詳細分析的。

OK,回到開篇的問題,我們來看一下基于Netfilter的防火牆系統到底定義了哪些鈎子函數?而這些鈎子函數都是分别挂載在哪些hook點的?按照其功能結構劃分,我将這些hook函數總結如下:

(二)洞悉linux下的Netfilter&iptables:核心中的ip_tables小觑

包過濾子功能:包過濾一共定義了四個hook函數,這四個hook函數本質最後都調用了ipt_do_table()函數。

網絡位址轉換子功能:該子產品也定義了四個hook函數,其中有三個最終也都調用了ip_nat_fn()函數,ip_nat_adjust()有自己另外的功能。

連接配接跟蹤子功能:這裡連接配接跟蹤應該稱其為一個子系統更合适些。它也定義四個hook函數,其中ip_conntrack_local()最後其實也調用了ip_conntrack_in()函數。

以上便是 Linux 的防火牆 ---iptables 在核心中定義的所有 hook 函數。接下來我們再梳理一下這些 hook 函數分别是被挂載在哪些 hook 點上的。還是先貼個三維框圖,因為我覺得這個圖是了解 Netfilter 核心機制最有效,最直覺的方式了,是以屢用不爽!

(二)洞悉linux下的Netfilter&iptables:核心中的ip_tables小觑

然後,我們拿一把大刀,從協定棧的IPv4點上順着hook點延伸的方向一刀切下去,就會得到一個平面,如上圖所示。前面這些hook函數在這個平面上的分布情況如下所示:

(二)洞悉linux下的Netfilter&iptables:核心中的ip_tables小觑

這幅圖徹底暴露了ip_tables核心子產品中那些hook函數在各個hook點分布情況。與此同時,這個圖還告訴了我們很多資訊:所有由網卡收上來的資料包率先被ip_conntrack_defrag處理;連結跟蹤系統的入口函數以-200的優先級被注冊到了PRE_ROUTING和LOCAL_OUT兩個hook點上,且其優先級高于mangle操作,NAT和包過濾等其他子產品;DNAT可以在PRE_ROUTING和LOCAL_OUT兩個hook點來做,SNAT可以在LOCAL_IN和POST_ROUTING兩個hook點上。如果你認真研究會發現這個圖确實很有用。因為當初為了畫這個圖我可是兩個晚上沒睡好覺啊,畫出來後還要驗證自己的想法,就得一步一步給那些關鍵的hook點和hook函數分别加上調試列印資訊,重新編譯核心然後确認這些hook函數确實是按照我所分析的那樣被調用的。因為對學術嚴謹就是對自己負責,一直以來我也都這麼堅信的。“沒有調查就沒發言權”;在我們IT行業,“沒有親自動手做過就更沒有發言權”。又扯遠了,趕緊收回來。

    架構的東西多看些之上從宏觀上可以使我們對整個系統的架構和設計有個比較全面的把握,接下來在分析每個細節的時候才會做到心中有數,不至于“盲人摸象”的境地。在本章即将結束之際,我們來看點代碼級的東西。我保證隻是個簡單的入門了解,因為重頭戲我打算放到後面,大家也知道分析代碼其實是最頭疼的,關鍵還是看自己的心态。

Netfilter的實作方式:

    第一篇我們講了Netfilter的原理,這裡我們談談其實作機制的問題。

我們回頭分析一下那個用于存儲不同協定簇在每個hook點上所注冊的hook函數鍊的二維數組 nf_hooks[][],其類型為list_head:

    struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS];

    list_head{}結構體定義在include/linux/list.h頭檔案中

struct list_head {

           struct list_head *next, *prev;

};

這是Linux核心中處理雙向連結清單的标準方式。當某種類型的資料結構需要被組織成雙向連結清單時,會在該資料結構的第一個字段放置一個list_head{}類型的成員。在後面的使用過程中可以通過強制類型轉換來實作雙向連結清單的周遊操作。

在Netfilter中一個非常重要的資料結構是nf_hook_ops{} <include/linux/netfilter.h>:

struct nf_hook_ops

{

struct list_head list;

nf_hookfn *hook;

struct module *owner;

int pf;

int hooknum;

int priority;

};

對該結構體中的成員參數做一下解釋:

n  list:因為在一個HOOK點有可能注冊多個鈎子函數,是以這個變量用來将某個HOOK點所注冊的所有鈎子函數組織成一個雙向連結清單;

n  hook:該參數是一個指向nf_hookfn類型的函數的指針,由該函數指針所指向的回調函數在該hook被激活時調用【nf_hookfn在後面做解釋】;

n  owner:表示這個hook是屬于哪個子產品的

n  pf:該hook函數所處理的協定。目前我們主要處理IPv4,是以該參數總是PF_INET;

n  hooknum:鈎子函數的挂載點,即HOOK點;

n  priority:優先級。前面也說過,一個HOOK點可能挂載了多個鈎子函數,當Netfilter在這些HOOK點上周遊查找所注冊的鈎子函數時,這些鈎子函數的先後執行順序便由該參數來制定。

nf_hookfn所定義的回調函數的原型在include/linux/netfilter.h檔案中:

typedef unsigned int nf_hookfn(unsigned int hooknum,    //HOOK不解釋

      const struct net_device *in,         //資料包的網絡如接口

      const struct net_device *out,       //<span times="" new="" roman";="" mso-hansi-font-family:"times="" roman";mso-bidi-font-family:"times="" mso-font-kerning:0pt"="" style="font-size: 10pt; font-family: 宋體;">資料包的網絡出接口

          int (*okfn)(struct sk_buff *));     //後續的處理函數

我們可以到,上面這五個參數最後将由NF_HOOK或NF_HOOK_COND宏傳遞到Netfilter架構中去。

如果要增加新的鈎子函數到Netfilter中相應的過濾點,我們要做的工作其實很簡單:

1)、編寫自己的鈎子函數;

2)、執行個體化一個struct nf_hook_ops{}結構,并對其進行适當的填充,第一個參數list并不是使用者所關心的,初始化時必須設定成{NULL,NULL};

3)、用nf_register_hook()<net/netfilter/core.c>函數将我們剛剛填充的nf_hook_ops結構體注冊到相應的HOOK點上,即nf_hooks[prot][hooknum]。

這也是最原生的擴充方式。有了上面這個對nf_hook_ops{}及其用法的分析,後面我們再分析其他子產品,如filter子產品、nat子產品時就會不那麼難懂了。

核心在網絡協定棧的關鍵點引入NF_HOOK宏,進而搭建起了整個Netfilter架構。但是NF_HOOK宏僅僅隻是一個跳轉而已,更重要的内容是“核心是如何注冊鈎子函數的呢?這些鈎子函數又是如何被調用的呢?誰來維護和管理這些鈎子函數?”

未完,待續…

繼續閱讀