-1: 爲什麼要寫這個東西?
最近在找工作,之前netfilter 這一塊的代碼也認真地研究過,應該每個人都是這樣的你懂 不一定你能很準確的表達出來。 故一定要化些時間把這相關的東西總結一下。
0:相關文檔
linux 下 nf_conntrack_tuple 跟蹤記錄 其中可以根據內核提供的數據結構獲取連接跟蹤記錄。
iptables 中的NAT使用總結 iptable的在防火牆上面的應用。
1:iptable中三個tables所掛接的HOOKs
其實這個問題很簡單的運行iptables打開看看就知道,此處的hook與內核的hook是對應起來的。
因此在內核中註冊的5個HOOK點如下:
enum nf_inet_hooks {
NF_INET_PRE_ROUTING,
NF_INET_LOCAL_IN,
NF_INET_FORWARD,
NF_INET_LOCAL_OUT,
NF_INET_POST_ROUTING,
NF_INET_NUMHOOKS
};
在向下看linux內核中的實現之前在看看一個數據包在進過linux內核中neitfilter的處理過程。
其中這5個HOOK點的執行點說明如下:
數據報從進入系統,進行IP校驗以後,首先經過第一個HOOK函數NF_IP_PRE_ROUTING進行處理;
然後就進入路由代碼,其決定該數據報是需要轉發還是發給本機的;
若該數據報是發被本機的,則該數據經過HOOK函數NF_IP_LOCAL_IN處理以後然後傳遞給上層協議;
若該數據報應該被轉發則它被NF_IP_FORWARD處理;
經過轉發的數據報經過最後一個HOOK函數NF_IP_POST_ROUTING處理以後,再傳輸到網絡上。
本地產生的數據經過HOOK函數NF_IP_LOCAL_OUT 處理後,進行路由選擇處理,然後經過NF_IP_POST_ROUTING處理後發送出去。
上面的圖可以知道,一個數據包在內核中進行的hook的處理點。
2 :proc文件下的跟蹤記錄
上面的就是連接跟蹤記錄,其中記錄linux系統建立的每一條連接,其中包括源IP,目的IP,源port,目的port,協議ID,其這些可以稱爲5元組。有關這個在linux內含中的定義是的結構體 struct nf_conn 中的變量
/* Connection tracking(鏈接跟蹤)用來跟蹤、記錄每個鏈接的信息(目前僅支持IP協議的連接跟蹤)。
每個鏈接由“tuple”來唯一標識,這裏的“tuple”對不同的協議會有不同的含義,例如對tcp,udp
來說就是五元組: (源IP,源端口,目的IP, 目的端口,協議號),對ICMP協議來說是: (源IP, 目
的IP, id, type, code), 其中id,type與code都是icmp協議的信息。鏈接跟蹤是防火牆實現狀態檢
測的基礎,很多功能都需要藉助鏈接跟蹤才能實現,例如NAT、快速轉發、等等。*/
/* XXX should I move this to the tail ? - Y.K */
/* These are my tuples; original and reply */
struct nf_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX]; 此變量就保存着上面的跟蹤記錄,
3:hooks點的定義及註冊
其中每個不同協議的不同HOOK點最終都會註冊到全局的nf_hooks鏈表變量之中:同時註冊到同一個HOOK的處理函數會根據優先級的不同的進行先後處理。
extern struct list_head nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS];
其中定義的協議如下:
enum {
NFPROTO_UNSPEC = 0,
NFPROTO_IPV4 = 2, //ipV4
NFPROTO_ARP = 3, //ARP
NFPROTO_BRIDGE = 7, //brigde
NFPROTO_IPV6 = 10,
NFPROTO_DECNET = 12,
NFPROTO_NUMPROTO,
};
NF_MAX_HOOKS 宏的定義已經在前面說明。HOOK點的定義。
與下面的HOOK的struct nf_hook_ops對比一下就可以看到差異:變量pf的值不同,優先級不同,即內核中根據不同的協議類型可以註冊不同的掛載點進行不同的優先級數據包的處理。
其註冊使用函數爲:nf_register_hooks()函數在內核中多個地方出現,因爲用戶可以根據自己的需要對特定的協議在特定的位置添加HOOK出現函數。
nf_register_hook()函數的實現就是:
- 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;////遍歷已經註冊的的HOOK,OPS,將新加入的根據優先級添加到鏈表最後
- 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;
- }
上面就是HOOK點的註冊函數,即根據協議類型和HOOK點註冊到全局數組中nf_hooks[][]中。
4:註冊的HOOK點何時被使用?
在linux內核中當需要使用註冊的HOOK點時,使用函數:
#define
NF_HOOK(pf, hook, skb, indev, outdev, okfn) \
NF_HOOK_THRESH(pf, hook, skb, indev, outdev, okfn, INT_MIN)
NF_HOOK->NF_HOOK_THRESH->nf_hook_thresh->nf_hook_slow——這個是最終的執行函數。
先看看下面函數都返回值:
/* Responses from hook functions. */
#define NF_DROP 0
#define NF_ACCEPT 1
#define NF_STOLEN 2
#define NF_QUEUE 3
#define NF_REPEAT 4
#define NF_STOP 5
#define NF_MAX_VERDICT NF_STOP
- 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];//是不是很熟悉就是上面的全局變量專門用來註冊全局HOOK點的變量。
- next_hook:/* 開始遍歷對應的netfilter的規則,即對應的proto和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_iterate()函數:
- unsigned int nf_iterate(struct list_head *head,
- struct sk_buff *skb,
- unsigned int hook,
- const struct net_device *indev,
- const struct net_device *outdev,
- struct list_head **i,
- int (*okfn)(struct sk_buff *),
- int hook_thresh)
- {
- unsigned int verdict;
- //其中head就是全局的2維數組nf_hooks,
- /*
- * The caller must not block between calls to this
- * function because of risk of continuing from deleted element.
- */
- list_for_each_continue_rcu(*i, head) {
- struct nf_hook_ops *elem = (struct nf_hook_ops *)*i;
- if (hook_thresh > elem->priority)
- continue;
- /* Optimization: we don't need to hold module
- reference here, since function can't sleep. --RR */
- verdict = elem->hook(hook, skb, indev, outdev, okfn);//根據協議和HOOK點執行掛在的處理函數
- if (verdict != NF_ACCEPT) {//返回結果進行判斷。
- #ifdef CONFIG_NETFILTER_DEBUG
- if (unlikely((verdict & NF_VERDICT_MASK)
- > NF_MAX_VERDICT)) {
- NFDEBUG("Evil return from %p(%u).\n",
- elem->hook, hook);
- continue;
- }
- #endif
- if (verdict != NF_REPEAT)
- return verdict;
- *i = (*i)->prev;
- }
- }
- return NF_ACCEPT;
- }
對各個返回值的解釋如下:
在數據包流經內核協議棧的整個過程中,在內中定義的HOOK中的如:PRE_ROUTING、LOCAL_IN、FORWARD、LOCAL_OUT和POST_ROUTING會根據數據包的協議簇PF_INET到這些關鍵點去查找是否註冊有鉤子函數。如果沒有,則直接返回okfn函數指針所指向的函數繼續走協議棧;如果有,則調用nf_hook_slow函數,從而進入到Netfilter框架中去進一步調用已註冊在該過濾點下的鉤子函數,再根據其返回值來確定是否繼續執行由函數指針okfn所指向的函數