寫在前面
本系列不是介紹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
就會被執行。