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所指向的函數