(一)洞悉linux下的Netfilter&iptables:什麼是Netfilter?

轉自:http://blog.chinaunix.net/uid-23069658-id-3160506.htmlhtml

 

本人研究linux的防火牆系統也有一段時間了,因爲近來涉及到的工做比較紛雜,長此以往怕生熟了。趁有時間,好好把這方面的東西總結一番。一來是給本身作個沉澱,二來也歡迎這方面比較牛的前輩給小弟予以指點,共同窗習,共同進步。linux

    能在CU上混的人絕非等閒之輩。所以,小弟這裏說明一下:本系列博文主要側重於分析Netfilter的實現機制,原理和設計思想層面的東西,同時從用戶態的iptables到內核態的Netfilter其交互過程和通訊手段等。至於iptables的入門用法方面的東西,網上隨便一搜羅就有一大堆,我這裏不浪費筆墨了。數組

    不少人在接觸iptables以後就會這麼一種感受:我經過iptables命令配下去的每一條規則,究竟是如何生效的呢?內核又是怎麼去執行這些規則匹配呢?若是iptables不能知足我當下的需求,那麼我是否能夠去對其進行擴展呢?這些問題,都是我在接下來的博文中一一和你們分享的話題。這裏須要指出:由於Netfilter與IP協議棧是無縫契合的,因此若是你要是有協議棧方面的基礎,在閱讀本文時必定會感受輕車熟路。固然,若是沒有也不要緊,由於我會在關鍵點就協議棧的入門知識給你們作個普及。只是普及哦,不會詳細深刻下去的,由於涉及的東西太多了,目前我還正在研究摸索當中呢。好了,廢話很少說,進入正題。網絡

備註:我研究的內核版本是2.6.21,iptables的版本1.4.0。數據結構

 

什麼是Netfilter?架構

    爲了說明這個問題,首先看一個網絡通訊的基本模型:app

 

    在數據的發送過程當中,從上至下依次是「加頭」的過程,每到達一層數據就被會加上該層的頭部;與此同時,接受數據方就是個「剝頭」的過程,從網卡收上包來以後,在往協議棧的上層傳遞過程當中依次剝去每層的頭部,最終到達用戶那兒的就是裸數據了。框架

那麼,「棧」模式底層機制基本就是像下面這個樣子:socket

 

對於收到的每一個數據包,都從「A」點進來,通過路由判決,若是是發送給本機的就通過「B」點,而後往協議棧的上層繼續傳遞;不然,若是該數據包的目的地是不本機,那麼就通過「C」點,而後順着「E」點將該包轉發出去。函數

對於發送的每一個數據包,首先也有一個路由判決,以肯定該包是從哪一個接口出去,而後通過「D」點,最後也是順着「E」點將該包發送出去。

協議棧那五個關鍵點A,B,C,D和E就是咱們Netfilter大展拳腳的地方了。

Netfilter是Linux 2.4.x引入的一個子系統,它做爲一個通用的、抽象的框架,提供一整套的hook函數的管理機制,使得諸如數據包過濾、網絡地址轉換(NAT)和基於協議類型的鏈接跟蹤成爲了可能。Netfilter在內核中位置以下圖所示:

 

這幅圖,很直觀的反應了用戶空間的iptables和內核空間的基於Netfilter的ip_tables模塊之間的關係和其通信方式,以及Netfilter在這其中所扮演的角色。

回到前面討論的關於協議棧那五個關鍵點「ABCDE」上來。Netfilter在netfilter_ipv4.h中將這個五個點從新命了個名,以下圖所示,意思我就再也不解釋了,貓叫咪咪而已:

 

在每一個關鍵點上,有不少已經按照優先級預先註冊了的回調函數(後面再說這些函數是什麼,幹什麼用的。有些人喜歡把這些函數稱爲「鉤子函數」,說的是同一個東西)埋伏在這些關鍵點,造成了一條鏈。對於每一個到來的數據包會依次被那些回調函數「調戲」一番再視狀況是將其放行,丟棄仍是怎麼滴。可是不管如何,這些回調函數最後必須向Netfilter報告一下該數據包的死活狀況,由於畢竟每一個數據包都是Netfilter從人家協議棧那兒借調過來給兄弟們Happy的,別個再怎麼滴也總得「活要見人,死要見屍」吧。每一個鉤子函數最後必須向Netfilter框架返回下列幾個值其中之一:

n  NF_ACCEPT 繼續正常傳輸數據報。這個返回值告訴 Netfilter:到目前爲止,該數據包仍是被接受的而且該數據包應當被遞交到網絡協議棧的下一個階段。

n  NF_DROP 丟棄該數據報,再也不傳輸。

n  NF_STOLEN 模塊接管該數據報,告訴Netfilter「忘掉」該數據報。該回調函數將今後開始對數據包的處理,而且Netfilter應當放棄對該數據包作任何的處理。可是,這並不意味着該數據包的資源已經被釋放。這個數據包以及它獨自的sk_buff數據結構仍然有效,只是回調函數從Netfilter 獲取了該數據包的全部權。

n  NF_QUEUE 對該數據報進行排隊(一般用於將數據報給用戶空間的進程進行處理)

n  NF_REPEAT 再次調用該回調函數,應當謹慎使用這個值,以避免形成死循環。

爲了讓咱們顯得更專業些,咱們開始作些約定:上面提到的五個關鍵點後面咱們就叫它們爲hook點,每一個hook點所註冊的那些回調函數都將其稱爲hook函數。

Linux 2.6版內核的Netfilter目前支持IPv四、IPv6以及DECnet等協議棧,這裏咱們主要研究IPv4協議。關於協議類型,hook點,hook函數,優先級,經過下面這個圖給你們作個詳細展現:

 

 

對於每種類型的協議,數據包都會依次按照hook點的方向進行傳輸,每一個hook點上Netfilter又按照優先級掛了不少hook函數。這些hook函數就是用來處理數據包用的。

Netfilter使用NF_HOOK(include/linux/netfilter.h)宏在協議棧內部切入到Netfilter框架中。相比於2.4版本,2.6版內核在該宏的定義上顯得更加靈活一些,定義以下:

#define NF_HOOK(pf, hook, skb, indev, outdev, okfn) \

         NF_HOOK_THRESH(pf, hook, skb, indev, outdev, okfn, INT_MIN)

關於宏NF_HOOK各個參數的解釋說明:

1)         pf:協議族名,Netfilter架構一樣能夠用於IP層以外,所以這個變量還能夠有諸如PF_INET6,PF_DECnet等名字。

2)         hook:HOOK點的名字,對於IP層,就是取上面的五個值;

3)         skb:不解釋;

4)         indev:數據包進來的設備,以struct net_device結構表示;

5)         outdev:數據包出去的設備,以struct net_device結構表示;

(後面能夠看到,以上五個參數將傳遞給nf_register_hook中註冊的處理函數。)

6)         okfn:是個函數指針,當全部的該HOOK點的全部登記函數調用完後,轉而走此流程。

而NF_HOOK_THRESH又是一個宏:

#define NF_HOOK_THRESH(pf, hook, skb, indev, outdev, okfn, thresh)                \

({int __ret;                                                                                \

if ((__ret=nf_hook_thresh(pf, hook, &(skb), indev, outdev, okfn, thresh, 1)) == 1)\

         __ret = (okfn)(skb);                                                       \

__ret;})

咱們發現NF_HOOK_THRESH宏只增長了一個thresh參數,這個參數就是用來指定經過該宏去遍歷鉤子函數時的優先級,同時,該宏內部又調用了nf_hook_thresh函數:

static inline int nf_hook_thresh(int pf, unsigned int hook,

                            struct sk_buff **pskb,

                            struct net_device *indev,

                            struct net_device *outdev,

                            int (*okfn)(struct sk_buff *), int thresh,

                            int cond)

{

if (!cond) 

return 1;

#ifndef CONFIG_NETFILTER_DEBUG

if (list_empty(&nf_hooks[pf][hook]))

         return 1;

#endif

return nf_hook_slow(pf, hook, pskb, indev, outdev, okfn, thresh);

}

這個函數又只增長了一個參數cond,該參數爲0則放棄遍歷,而且也不執行okfn函數;爲1則執行nf_hook_slow去完成鉤子函數okfn的順序遍歷(優先級從小到大依次執行)。

在net/netfilter/core.h文件中定義了一個二維的結構體數組,用來存儲不一樣協議棧鉤子點的回調處理函數。

struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS];

其中,行數NPROTO爲32,即目前內核所支持的最大協議簇;列數NF_MAX_HOOKS爲掛載點的個數,目前在2.6內核中該值爲8。nf_hooks數組的最終結構以下圖所示。

 

在include/linux/socket.h中IP協議AF_INET(PF_INET)的序號爲2,所以咱們就能夠獲得TCP/IP協議族的鉤子函數掛載點爲:

PRE_ROUTING:     nf_hooks[2][0]

LOCAL_IN:        nf_hooks[2][1]

FORWARD:      nf_hooks[2][2]

LOCAL_OUT:      nf_hooks[2][3]

POST_ROUTING:          nf_hooks[2][4]

同時咱們看到,在2.6內核的IP協議棧裏,從協議棧正常的流程切入到Netfilter框架中,而後順序、依次去調用每一個HOOK點全部的鉤子函數的相關操做有以下幾處:

       1)、net/ipv4/ip_input.c裏的ip_rcv函數。該函數主要用來處理網絡層的IP報文的入口函數,它到Netfilter框架的切入點爲:

NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL,ip_rcv_finish)

根據前面的理解,這句代碼意義已經很直觀明確了。那就是:若是協議棧當前收到了一個IP報文(PF_INET),那麼就把這個報文傳到Netfilter的NF_IP_PRE_ROUTING過濾點,去檢查[R]在那個過濾點(nf_hooks[2][0])是否已經有人註冊了相關的用於處理數據包的鉤子函數。若是有,則挨個去遍歷鏈表nf_hooks[2][0]去尋找匹配的match和相應的target,根據返回到Netfilter框架中的值來進一步決定該如何處理該數據包(由鉤子模塊處理仍是交由ip_rcv_finish函數繼續處理)。

[R]:剛纔說到所謂的「檢查」。其核心就是nf_hook_slow()函數。該函數本質上作的事情很簡單,根據優先級查找雙向鏈表nf_hooks[][],找到對應的回調函數來處理數據包:

struct list_head **i;

list_for_each_continue_rcu(*i, head) {

struct nf_hook_ops *elem = (struct nf_hook_ops *)*i;

if (hook_thresh > elem->priority)

                  continue;

         verdict = elem->hook(hook, skb, indev, outdev, okfn);

         if (verdict != NF_ACCEPT) { … … }

    return NF_ACCEPT;

}

上面的代碼是net/netfilter/core.c中的nf_iterate()函數的部分核心代碼,該函數被nf_hook_slow函數所調用,而後根據其返回值作進一步處理。

2)、net/ipv4/ip_forward.c中的ip_forward函數,它的切入點爲:

NF_HOOK(PF_INET, NF_IP_FORWARD, skb, skb->dev, rt->u.dst.dev,ip_forward_finish);

在通過路由抉擇後,全部須要本機轉發的報文都會交由ip_forward函數進行處理。這裏,該函數由NF_IP_FOWARD過濾點切入到Netfilter框架,在nf_hooks[2][2]過濾點執行匹配查找。最後根據返回值來肯定ip_forward_finish函數的執行狀況。

3)、net/ipv4/ip_output.c中的ip_output函數,它切入Netfilter框架的形式爲:

NF_HOOK_COND(PF_INET, NF_IP_POST_ROUTING, skb, NULL, dev,ip_finish_output,

                                !(IPCB(skb)->flags & IPSKB_REROUTED));

這裏咱們看到切入點從無條件宏NF_HOOK改爲了有條件宏NF_HOOK_COND,調用該宏的條件是:若是協議棧當前所處理的數據包skb中沒有從新路由的標記,數據包纔會進入Netfilter框架。不然直接調用ip_finish_output函數走協議棧去處理。除此以外,有條件宏和無條件宏再無其餘任何差別。

若是須要陷入Netfilter框架則數據包會在nf_hooks[2][4]過濾點去進行匹配查找。

4)、仍是在net/ipv4/ip_input.c中的ip_local_deliver函數。該函數處理全部目的地址是本機的數據包,其切入函數爲:

NF_HOOK(PF_INET, NF_IP_LOCAL_IN, skb, skb->dev, NULL,ip_local_deliver_finish);

發給本機的數據包,首先所有會去nf_hooks[2][1]過濾點上檢測是否有相關數據包的回調處理函數,若是有則執行匹配和動做,最後根據返回值執行ip_local_deliver_finish函數。

5)、net/ipv4/ip_output.c中的ip_push_pending_frames函數。該函數是將IP分片重組成完整的IP報文,而後發送出去。進入Netfilter框架的切入點爲:

NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, skb->dst->dev, dst_output);

對於全部從本機發出去的報文都會首先去Netfilter的nf_hooks[2][3]過濾點去過濾。通常狀況下來來講,不論是路由器仍是PC中端,不多有人限制本身機器發出去的報文。由於這樣作的潛在風險也是顯而易見的,每每會由於一些不恰當的設置致使某些服務失效,因此在這個過濾點上攔截數據包的狀況很是少。固然也不排除真的有特殊需求的狀況。

 

小節:整個Linux內核中Netfilter框架的HOOK機制能夠歸納以下:

在數據包流經內核協議棧的整個過程當中,在一些已預約義的關鍵點上PRE_ROUTING、LOCAL_IN、FORWARD、LOCAL_OUT和POST_ROUTING會根據數據包的協議簇PF_INET到這些關鍵點去查找是否註冊有鉤子函數。若是沒有,則直接返回okfn函數指針所指向的函數繼續走協議棧;若是有,則調用nf_hook_slow函數,從而進入到Netfilter框架中去進一步調用已註冊在該過濾點下的鉤子函數,再根據其返回值來肯定是否繼續執行由函數指針okfn所指向的函數。

未完,待續…

相關文章
相關標籤/搜索