寫在前面
本系列不是介紹How to
配置iptables
的文章。因爲網絡上已經有很多這類型的教程了,其中一些還不錯(比如鏈接).
本系列也不是一般意義上的Netfilter
源碼分析文章。因爲大段粘貼代碼也會讓人心生畏懼和厭煩!
本系列文章的目標是,用盡量少的文字和圖片講明白How Netfilter work
Netfilter 的基本概念
Netfilter
是一套融入在Linux
內核網絡協議棧中的報文處理(過濾
或者修改
)框架。它在內核中報文的關鍵流動路徑上定義了5
個HOOK
點(下圖藍色方框),各個協議(如IPv4
、IPv6
、ARP
)可以在這些HOOK
點安裝鉤子函數,報文流經此地,內核會按照優先級調用這些鉤子函數,這些鉤子函數最終會決定報文是被NF_ACCEPT
(放行)還是NF_DROP
(丟棄)。
圖中紅色虛線表示內核最常見的報文流經的路徑:本機接收、轉發、本機發送。5
個HOOK
點分別是:路由前、本地上送、轉發、本地發送、路由後1
鏈(chain) & 表(table)
初次接觸iptables
的同學可能會被四表五鏈
這個名字嚇到,特別是鏈
這個名字真的很容易令人困惑! 而當你瞭解了Netfilter
的實現細節後,纔會發現:噢,原來鏈
就是HOOK
點,HOOK
點就是鏈
,因爲有5
個HOOK
點,所以有五鏈
!
那麼,爲什麼要叫鏈
呢?
因爲一個HOOK
點可以上可以安裝多個鉤子, 內核用“鏈條”將這些鉤子串起來!
相比之下,四表(table)
就沒那麼神祕了: 起過濾作用的filter
表、起NAT
作用的nat
表,用於修改報文的mangle
表,用於取消連接跟蹤的raw
表。
Netfilter
設計多個表的目的,一方面是方便分類管理,另一方面,更重要的是爲了限定各個鉤子(或者說用戶規則)執行的順序!
以PREROUTING
這個HOOK
點爲例,用戶使用iptables
設置的NAT
規則和mangle
會分別掛到nat hook
和mangle hook
,NAT
表的優先級天生比mangle
表低,因此報文一定會先執行mangle
表的規則。
這就是四表五鏈
的概念。我個人認爲鏈
的比表
重要多了. 因爲就算Netfilter
沒有表的概念,那麼通過小心翼翼地設置各個rule
的順序其實也可以達到相同的效果。但鏈
(也就是HOOK
點)的作用是獨一無二的。換個角度,用戶在配置iptables
規則時,更多的精力也是放在"應該在哪個HOOK點進行操作",至於用的是filter
表、nat
表還是其他表,其實都是順理成章的事情。
Hook
HOOK 點的位置
用戶通過iptables
配置的規則最終會記錄在HOOK
點。HOOK
點定義在struct net
結構中,即HOOK
點是各個net namespace
中獨立的。所以,在使用容器的場景中,每個容器的防火牆規則是獨立的。
struct net {
/* code omitted */
struct netns_nf nf;
/* code omitted */
}
struct netns_nf {
/* code omitted */
struct list_head hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS];
};
從上面的定義可以看到,HOOK
點是一個二維數組,每個元素都是一個鏈表頭。它的第一個維度是協議類型,其中最常用的NFPROTO_IPV4
,我們使用的iptables
命令都是將這個鉤子安裝到這個協議的hook
,而使用ip6tables
就是將鉤子安裝到NFPROTO_IPV6
的hook
;第二個維度是鏈
,對於IPV4
來說,它的取值範圍如下:
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,
}
HOOK 點的元素
hooks
的每個元素都是鏈表頭,鏈表上掛的元素類型是struct nf_hook_ops
,這些元素有兩個來源,一類來自於Netfilter
初始化時各個表(如filter
)的初始化,另一類來自於如連接跟蹤這樣的內部模塊。下圖展示了第一類來源的元素的掛接情況,它們按優先級排列(數字越小優先級越高),而.hook
就是報文到達對應的路徑時會執行的鉤子函數。
附:相關內核函數的例子
iptable_filter_init
|--xt_hook_link
|-- nf_register_hooks
|-- nf_register_hook
HOOK 點的調用
Netfilter
框架已經完全融入內核協議棧了,所以在協議棧代碼中常常可以看到NF_HOOK
宏的調用,這個宏的參數指定了HOOK
點。
以本機收到IPv4
報文爲例
int ip_rcv(struct sk_buff* skb,...)
{
// code omitted
return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, net, NULL, skb, dev, NULL, ip_rcv_finish);
// code omitted
}
它指定要遍歷的鉤子函數是net namespace爲net
的hooks[NFPROTO_IPV4][NF_INET_PRE_ROUTING]
鏈表上的元素,也就是上面圖中的第一行的鏈表。如果三個鉤子函數執行的結果(verdict
)都是NF_ACCEPT
,那麼NF_HOOK
指定的ip_rcv_finish
就會被執行。