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