內核版本: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進行動態加載。