歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux綜合 >> Linux內核 >> Linux內核分析 - 網絡[七]:NetFilter

Linux內核分析 - 網絡[七]:NetFilter

日期:2017/3/3 16:38:18   编辑:Linux內核

內核版本:2.6.34

NetFilter在2.4.x內核中引入,成為linux平台下進行網絡應用的主要擴展,不僅包括防火牆的實現 ,還包括報文的處理(如報文加密、報文分類統計等)等。

NetFilter數據結構 勾子struct nf_hook_ops[net\filter\core.c]

struct nf_hook_ops {     
    struct list_head list;     
    /* User fills in from here down. */ 
    nf_hookfn *hook;     
    struct module *owner;     
    u_int8_t pf;     
    unsigned int hooknum;     
    /* Hooks are ordered in ascending priority. */ 
    int priority;     
};

成員list用於鏈入全局勾子數組nf_hooks中,它一定在第一位,保證&nf_hook_ops->list的值與 &nf_hook_ops相同,稍後在使用時會用到這一技巧;

成員hook即用戶定義的勾子函數;owner表示注冊這個勾子函數的模 塊,因為netfilter是內核空間的,所以一般為模塊來完成勾子函數注冊;pf與hooknum一起索引到特定協議特定編號的勾子函數 隊列,用於索引nf_hooks;priority決定在同一隊列(pf與hooknum相同)的順序,priority越小則排列越靠前。

struct nf_hook_ops只是存儲勾子的數據結構,而真正存儲這些勾子供協議棧調用的是nf_hooks,從定義可以看出,它其實就是二維數 組的鏈表。

struct list_head nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS]; [net\filter\core.c]

其中NFPROTO_NUMPROTO表示勾子關聯的協議,可取值:

enum {     
    NFPROTO_UNSPEC =  0,     
    NFPROTO_IPV4   =  2,     
    NFPROTO_ARP    =  3,     
    NFPROTO_BRIDGE =  7,     
    NFPROTO_IPV6   = 10,     
    NFPROTO_DECNET = 12,     
    NFPROTO_NUMPROTO,     
};

NF_MAX_HOOKS表示勾子應用的位置,可選值在每個協議模塊內部定義,這些值代表了勾子函數在協議流程中應用的 位置(稍後會以bridge為例詳細說明),大致上都有以下值:

NF_XXX_PRE_ROUTING,     
NF_XXX_LOCAL_IN,     
NF_XXX_FORWARD,     
NF_XXX_LOCAL_OUT,     
NF_XXX_POST_ROUTING,     
NF_XXX_NUMHOOKS

NetFilter注冊

在了解了nf_hook_ops和nf_hooks後,來看下如何操作nf_hooks中的元素。

nf_register_hook()將nf_hook_ops注冊到nf_hooks中:

int nf_register_hook(struct nf_hook_ops *reg)     
{     
    struct nf_hook_ops *elem;     
    int err;     


    err = mutex_lock_interruptible(&nf_hook_mutex);     
    if (err < 0)     
        return err;     
    list_for_each_entry(elem, &nf_hooks[reg->pf][reg->hooknum], list) {     
        if (reg->priority < elem->priority)     
            break;     
    }     
    list_add_rcu(?->list, elem->list.prev);     
    mutex_unlock(&nf_hook_mutex);     
    return 0;     
}

這個函數很簡單,從指定pf&hooknum的nf_hooks隊列遍歷,按priority從小到大順序,將reg插入相應位置,完 成勾子函數的注冊。

nf_unregister_hook()將nf_hook_ops從nf_hooks中注銷掉:

void nf_unregister_hook

(struct nf_hook_ops *reg)     
{     
    mutex_lock(&nf_hook_mutex);     
    list_del_rcu(?->list);     
    mutex_unlock(&nf_hook_mutex);     
    synchronize_net();     
}

這個函數更簡單,從nf_hooks中刪除reg。

內核同時還提供了nf_register_hooks()和nf_unregister_hooks(),將reg重復注冊n次或將reg從nf_hooks中注銷n次。當勾子函數注冊完成後,nf_hooks的結構如圖所示:

NetFilter調用

在報文在內核協議棧傳遞時,會調用NetFilter模塊對報文進行特定的進濾,這樣的過濾在代碼中隨處可見。

以上一篇講過的網橋為例,對於要進行網橋處理的報文,handle_bridge()->br_handle_frame(),如果端口處理於LEARNING 或FORWARDING狀態,且報文目的地址正確,則會調用br_handle_frame()進行後續處理,而這個函數調用就是:

NF_HOOK (PF_BRIDGE, NF_BR_PRE_ROUTING, skb, skb->dev, NULL,

br_handle_frame_finish);

NF_HOOK()- >NF_HOOK_THRESH()->nf_hook_thresh()->nf_hook_slow():

int nf_hook_slow(u_int8_t pf, unsigned 

int hook, struct sk_buff *skb,     
         struct net_device *indev,     
         struct net_device *outdev,     
         int (*okfn)(struct sk_buff *),     
         int hook_thresh)     
{     
    struct list_head *elem;     
    unsigned int verdict;     
    int ret = 0;     


    /* We may already have this, but read-locks nest anyway */ 
    rcu_read_lock();     


    elem = &nf_hooks[pf][hook];     
next_hook:     
    verdict = nf_iterate(&nf_hooks[pf][hook], skb, hook, indev,     
                 outdev, &elem, okfn, hook_thresh);     
    if (verdict == NF_ACCEPT || verdict == NF_STOP) {     
        ret = 1;     
    } else if (verdict == NF_DROP) {     
        kfree_skb(skb);     
        ret = -EPERM;     
    } else if ((verdict & NF_VERDICT_MASK) == NF_QUEUE) {     
        if (!nf_queue(skb, elem, pf, hook, indev, outdev, okfn,     
                  verdict >> NF_VERDICT_BITS))     
            goto next_hook;     
    }     
    rcu_read_unlock();     
    return ret;     
}

nf_hook_slow()從nf_hooks中找出到執行的勾子隊列,依次執行,然後根據返回值決定是否繼續(由nf_iterate()完 成)。參數中的pf和hook代表了注冊勾子函數時給的參數PF和HOOKNUM,它們共同決定勾子函數要插入的nf_hook的哪個隊列中。

作為過濾報文的勾子函數的返回值是值得注意的地方,可取值如下:

#define NF_DROP 0     
#define NF_ACCEPT 1     
#define NF_STOLEN 2     
#define NF_QUEUE 3     
#define NF_REPEAT 4     
#define NF_STOP 5

先以nf_iterate()函數為例,elem->hook()表示執行勾子函數,執行結構為verdict;

unsigned int nf_iterate(……)     
{     
    unsigned int verdict;     


    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) {
            if (verdict != NF_REPEAT)
                return verdict;
            *i = (*i)->prev;
        }
    }
    return NF_ACCEPT;     
}

根據nf_iterate()返回,會有以下情況:

1.如果結果為NF_ACCEPT,表示勾子函數允許報文繼續向下處理,此時應 該繼續執行隊列上的下一個勾子函數,因為這些勾子函數都是對同一類報文在相同位置的過濾,前一個通後,並不能返回,而要 所有函數都執行完,結果仍為NF_ACCEPT時,則可返回它;

2.如果結果為NF_REPEAT,表示要重復執行勾子函數一次;所以勾 子函數要編寫得當,否則報文會一直執行一個返回NF_REPEAET的勾子函數,當返回值為NF_REPEAT時,不會返回;

3.如果為其 它結果,則不必再執行隊列上的其它函數,直接返回它;如NF_STOP表示停止執行隊列上的勾子函數,直接返回;NF_DROP表示丟 棄掉報文;NF_STOLEN表示報文不再往上傳遞,與NF_DROP不同的是,它沒有調用kfree_skb()釋放掉skb;NF_QUEUE檢查給定協議 (pf)是否有隊列處理函數,有則進行處理,否則丟掉。

了解了這些值再來看nf_hook_slow()中對於nf_iterate()返回值的處 理就明了了:

if (verdict == NF_ACCEPT || verdict == NF_STOP) {     
    ret = 1;     
} else if (verdict == NF_DROP) {     
    kfree_skb(skb);     
    ret = -EPERM;     
} else if ((verdict & NF_VERDICT_MASK) == NF_QUEUE) {     
    if (!nf_queue(skb, elem, pf, hook, indev, outdev, okfn,     
             verdict >> NF_VERDICT_BITS))     
        goto next_hook;     
}

最後還是以bridge來說明下hooks參數的意義,上面已經講過,它決定了在協議流程的何處調用勾子函數;因為使用 NetFilter的目的是在內核態處理報文,而哪些地方可以處理報文只能是內核已經定義好的。一般來說,內核會在報文發送和接 收的關鍵位置添加勾子函數處理,查找代碼中NF_HOOK即可知。下面以bridge,為例,來看下在哪些地方用到了,以及這些值的 含義:

NetFilter的存在使得在內核空間對報文進行用戶定義的要求處理變得可能、簡單。一般來說,編寫好struct nf_hook_ops,其中hook/pf/ hook是必給的參數,然後使用nf_register_hook進行注冊就可以了。整個過濾文件可以寫了一個內 核模塊,用insmod進行動態加載。

Copyright © Linux教程網 All Rights Reserved