作者:Linux/Free Software 獨立技術顧問
2002年11月15日
本文介紹 Linux 的防火牆技術 netfilter/iptables 在 Linux 內核中的具體實現。
netfilter 和 Linux 防火牆介紹
Linux 的防火牆技術經歷了若干代的沿革,一步步的發展而來。最開始的 ipfwadm 是 Alan Cox 在 Linux kernel 發展的初期,從 FreeBSD 的內核代碼中移植過來的。後來經歷了 ipchains,再經由 Paul Russell 在 Linux kernel 2.3 系列的開發過程中發展了 netfilter 這個架構。而用戶空間的防火牆管理工具,也相應的發展為 iptables。netfilter/iptables 這個組合目前相當的令人滿意。在經歷了 Linux kernel 2.4 和 2.5 的發展以後,的確可以說,netfilter/iptables 經受住了大量用戶廣泛使用的考驗。
本文並不打算介紹 Linux 防火牆在用戶空間的管理程序 iptables 的使用。至於如何利用 netfilter/iptables 機制搭建一個可靠的 Internet 防火牆,這也不是本文感興趣的話題。關於 iptables 的使用,讀者朋友們可以參考 man iptables 的手冊,也可以參考 netfilter 的核心開發者 Paul Russell 寫的 Packet Filtering HOW-TO 和 NAT HOW-TO。相關的鏈接,請參見文後所列的參考資料目錄。讀者朋友們在閱讀本文之前,最好能夠對 iptables 的使用有一定的了解。
本文介紹 netfilter 在 Linux kernel 中的實現。如果條件允許的話,我們可能在後續的文章中將要進一步說明如何編寫自己的 kernel modules 並將其鑲嵌在 netfilter 的架構中,以實現自己的定制防火牆功能。值得指出的是,在 netfilter 的網站上,可以看到 netfilter 的一個子項目 patch-o-matic,其中收錄了大量的各種定制 kernel modules,這些 modules 給讀者朋友們開發自己的 kernel modules,提供了非常多的、很好的例子。
IPv4 代碼中 netfilter 的接口
netfilter 在 Linux kernel 中的 IPv4、IPv6 和 DECnet 等網絡協議棧中都有相應的實現。本文限於篇幅,將只介紹其中最讓大多數讀者朋友們感興趣的 IPv4 協議棧上的 netfilter 的實現。
我們在編譯 Linux kernel 的過程中一定會注意到,netfilter 是一個在編譯過程中可選的部件。也就是說,用戶在編譯內核的過程中,可以按照自己的需要,決定是否要在自己的內核中編譯進去 netfilter 的 kernel 支持。這就帶給我們一個提示,實現 netfilter 的代碼對於實現 IPv4 協議棧的代碼的影響應該會是盡量的小,不那麼引人注目才對。否則的話,IPv4 協議棧的代碼維護工作就不得不和實現 netfilter 的代碼的維護工作攪在一起,讓人頭疼了。
事實也的確如此,IPv4 協議棧為了實現對 netfilter 架構的支持,在 IP packet 在 IPv4 協議棧上的游歷路線之中,仔細選擇了五個參考點。在這五個參考點上,各引入了一行對 NF_HOOK() 宏函數的一個相應的調用。這五個參考點被分別命名為 PREROUTING,LOCAL-IN,FORWARD,LOCAL-OUT 和 POSTROUTING。關於這五個參考點的含義,在 iptables 的使用說明中有准確的敘述,相信讀者朋友們都應該了解了。從如下的 grep 輸出,我們可以看到 IPv4 協議棧實現代碼對 NF_HOOK() 宏函數的調用:
zhaoway@qhq ~/linux-2.4.19/net/ipv4 $ grep -n NF_HOOK *.c
arp.c:591:NF_HOOK(NF_ARP, NF_ARP_OUT, skb, NULL, dev, dev_queue_xmit);
arp.c:871:return NF_HOOK(NF_ARP, NF_ARP_IN, skb, dev, NULL, arp_process);
igmp.c:187:/* Don't just hand NF_HOOK skb->dst->output, in case netfilter hook
igmp.c:252:return NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev,
ip_forward.c:145:return NF_HOOK(PF_INET, NF_IP_FORWARD, skb, skb->dev, dev2,
ip_gre.c:668:/* Need this wrapper because NF_HOOK takes the function address */
ip_input.c:302:return NF_HOOK(PF_INET, NF_IP_LOCAL_IN, skb, skb->dev, NULL,
ip_input.c:437:return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL,
ip_output.c:111:/* Don't just hand NF_HOOK skb->dst->output, in case netfilter hook
ip_output.c:156:return NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev,
ip_output.c:191:return NF_HOOK(PF_INET, NF_IP_POST_ROUTING, skb, NULL, dev,
ip_output.c:233:NF_HOOK(PF_INET, NF_IP_POST_ROUTING, newskb, NULL,
ip_output.c:249:NF_HOOK(PF_INET, NF_IP_POST_ROUTING, newskb, NULL,
ip_output.c:400:return NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev,
ip_output.c:603:err = NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL,
ip_output.c:714:err = NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev,
ipip.c:516:/* Need this wrapper because NF_HOOK takes the function address */
ipmr.c:1211:NF_HOOK(PF_INET, NF_IP_FORWARD, skb2, skb->dev, dev,
zhaoway@qhq ~/linux-2.4.19/net/ipv4 $
NF_HOOK() 這個宏函數,定義在 linux-2.4.19/include/linux/netfilter.h 裡面。當 #ifdef CONFIG_NETFILTER 被定義的時候,就轉去調用 nf_hook_slow() 函數;如果 CONFIG_NETFILTER 沒有被定義,則從 netfilter 模塊轉回到 IPv4 協議棧,繼續往下處理。這樣就給了用戶在編譯 kernel 的時候一個選項,可以通過定義 CONFIG_NETFILTER 與否來決定是否把 netfilter 支持代碼編譯進內核。從這個函數的名稱,我們也可以猜到,可以把 IPv4 協議棧上的這五個參考點,形象的看成是五個鉤子。IP packet 在 IPv4 協議棧上游歷的時候,途經這五個鉤子,就會被 netfilter 模塊釣上來,審查一番,並據審查的結果,決定 packet 的下一步命運:是被原封不動的放回 IPv4 協議棧,繼續游歷;還是經過一些修改,再放回去;還是干脆丟棄掉算了?
netfilter 的核心模塊
“魚鉤”和“垂釣點”
IP packet 被 NF_HOOK() 從 IPv4 協議棧上釣出來以後,就進入 linux-2.4.19/net/core/netfilter.c 中的 nf_hook_slow() 函數進行處理。這個函數干的主要事情,就是根據 nf_hooks[] 數組,開始處理 packet。准確地說來,上一段講到的 IPv4 協議棧上的五個參考點,並不是“釣魚的鉤子”,而是“允許垂釣的地點”。換句話說,IPv4 協議棧上定義了五個“允許垂釣點”。在每一個“垂釣點”,都可以讓 netfilter 放置一個“魚鉤”,把經過的 packet 釣上來。那麼 netfiler 的“魚鉤”都放在什麼地方?就放在 nf_hooks[][] 數組裡面。這個“魚鉤”用 linux-2.4.19/include/linux/netfilter.h 中定義的如下 strUCt 予以描述:
struct nf_hook_ops
{
struct list_head list;
nf_hookfn *hook;
int pf;
int hooknum;
int priority;
};
我們看到,“魚鉤”的本質,是一個 nf_hookfn 函數。這個函數將對被釣上來的 IP packet 進行初步的處理。那麼,這些“魚鉤”是由誰來放置到 nf_hooks[][] 數組裡面的呢?答案是,各個 table。熟悉 iptables 管理工具的讀者朋友們應該了解,一個 table 就是一組類似的防火牆 rules 的集合。iptables 裡面默認定義了三個 table:filter,mangle,和 nat。舉 filter table 為例,它是在 linux-2.4.19/net/ipv4/netfilter/iptable_filter.c 中實現的一個 kernel module。在這個 module 的初始化過程中,它會調用 nf_register_hook() 向 netfilter 的核心代碼注冊一組“魚鉤”。這個注冊過程,實際上,也就是把“魚鉤”放到“垂釣點”的過程。“垂釣點”的具體位置,由 nf_hooks[][] 數組的下標具體說明。
ipt_do_table()
我們具體看到 linux-2.4.19/net/ipv4/netfilter/iptable_filter.c 也就是 filter table 的實現代碼,就發現 filter table 中的“魚鉤”上的 nf_hookfn 函數,主要是在調用 ipt_do_table() 函數。這是一個定義在 linux-2.4.19/net/ipv4/netfilter/ip_tables.c 中的函數。前面提到過,一個 table 就是一組防火牆 rules 的集合。顯然,ipt_do_table() 函數將要做的事情,就是按照 table 中存儲的一條又一條的 rules 來處理被“釣”上來的 IP packet。
table 裡面存放了這個 table 中所有的防火牆 rules。但是並不是所有的 rules 都要拿過來,按照它審查一下這個 packet。事實上,這個 packet 是從哪個“魚鉤”上被釣上來的,就只有和那個“魚鉤”相關的 rules 才被拿過來,用來審查這個 packet。這個機制,就為每個 table 實現了多個 chain,而每個 chain 上又有多個 rules。而且,我們立刻看到,一個 chain 是和 IPv4 協議棧上的一個“垂釣點”相對應的。熟悉 iptables 用戶空間管理工具的使用的讀者朋友們應該立刻就會注意到這一點了。
在 linux-2.4.19/include/linux/netfilter_ipv4/ip_tables.h 中定義了 table 中的 rule 的存放格式,如下:
/* This structure defines each of the firewall rules. Consists of 3
parts which are 1) general IP header stuff 2) match specific
stuff 3) the target to perform if the rule matches */
struct ipt_entry
{
struct ipt_ip ip;
/* Mark with fields that we care about. */
unsigned int nfcache;
/* Size of ipt_entry + matches */
u_int16_t target_offset;
/* Size of ipt_entry + matches + target */
u_int16_t next_offset;
/* Back pointer */
unsigned int comefrom;
/* Packet and byte counters. */
struct ipt_counters counters;
/* The matches (if any), then the target. */
unsigned char elems[0];
};
一個 entry 就是一個 rule。一個 entry 主要由兩部分組成。一部分,是一系列的 matches;另一部分,是一個 target。這若干個 match 所要回答的問題,是相關的 packet 和本條 rule 是否匹配。而這個 target 所要回答的問題,是一旦 packet 匹配上以後,該拿這個 packet 怎麼辦?也就是要由 target 來決定這個匹配的 packet 今後的命運了。開頭的 struct ipt_ip 的定義如下:
struct ipt_ip {
/* Source and destination IP addr */
struct in_addr src, dst;
/* Mask for src and dest IP addr */
struct in_addr smsk, dmsk;
char iniface[IFNAMSIZ], outiface[IFNAMSIZ];
unsigned char iniface_mask[IFNAMSIZ], outiface_mask[IFNAMSIZ];
/* Protocol, 0 = ANY */
u_int16_t proto;
/* Flags Word */
u_int8_t flags;
/* Inverse flags */
u_int8_t invflags;
};
我們立刻可以看出來,在 struct ipt_ip 裡面記錄了關於這個 rule 所要匹配(match)的 packet 的一些特征。
match 和 target
netfilter 核心部分提供了一個分析、處置 packet 的架構,但是核心部分代碼並不具體的去分析、處置 packet。這個具體的分析、處置的任務被交給其它的 module 來完成。核心部分代碼可以根據 table 中記錄的 rules 信息,來把 packet 交給能夠處理相應的 rules 的 module 代碼。那麼,核心代碼如何了解哪一個 module 可以處理哪一類的 rules 的呢?這要由各個相應的 modules 起動的時候,主動去向核心代碼注冊,ipt_register_target() 或者是 ipt_register_match()。這個注冊過程,主要就是通知核心代碼,本 module 有一個 target() 函數,可以決定 packet 的命運;或者是,本 module 有一個 match() 函數,可以判定一個 packet 是否符合 rules 的匹配要求。
這就提示我們,如果要寫自己的防火牆模塊,鑲嵌在 netfilter 的架構中的話,我們主要要做的任務,就是向 netfilter 核心注冊 ipt_register_target() 或者 ipt_register_match()。
iptables 管理工具
最後,要說明的是 iptables,這個位於用戶空間的管理工具。前面我們看到了,netfilter 在內核空間的代碼根據 table 中的 rules,完成對 packet 的分析和處置。但是這些 table 中的具體的防火牆 rules,還是必須由系統管理員親自編寫。kernel 中的 netfilter 只是提供了一個機制,它並不知道該怎樣利用這個機制,寫出合適的 rules,來實現一個網絡防火牆。那麼,系統管理員編寫的 rules,怎樣進入位於 kernel 空間中的 netfilter 維護的 table 中去呢?
這個任務是由 iptables 這個工具來完成的。它經過 getsockopt() 以及 setsockopt() 兩個系統調用,進入 kernel 空間。這兩個調用是 BSD Socket 接口的一部分。這裡面的問題是 IPv4 在接到關於某個 sock 的不認識的 opt 的時候,應該怎麼處理?netfilter 要求它在 linux-2.4.19/net/ipv4/ip_sockglue.c 文件中處理 getsockopt() 和 setsockopt() 系統調用的 ip_sockopt() 函數中適當的地方調用 nf_sockopt()。這樣,用戶空間就可以和 netfilter 核心部分進行交流,可以維護 table 中的防火牆 rules 了。
小結
netfilter 對於 IPv4 的修改非常小,一是在若干個地方調用了 NF_HOOK(),二是在 ip_sockopt() 中調用了 nf_sockopt()。netfilter 的核心部分代碼只是維護 table,解釋 table 的任務在於其它的 kernel module。netfilter 會把從 hook “釣”起來的 packet 以及 table 裡面的相關內容發給注冊了的 module,決定 packet 的命運。
參考資料
1 netfilter/iptables 主站點在 http://www.netfilter.org/ 或者 http://www.iptables.org/ 在這個站點上,可以找到 netfilter 核心開發者 Paul Russell 寫的 Linux Packet Filtering HOW-TO,Linux NAT HOW-TO 等關於怎樣部署 Linux netfilter 防火牆的技術文章。
2 Linux 內核源代碼的在線交叉索引在 http://lxr.linux.no/ 這個站點可以幫助讀者朋友們更加方便的閱讀 Linux kernel 的源程序。
關於作者
趙蔚,住在南京市的 Linux/Free Software 獨立技術顧問。在 IBM developerWorks 發表過多篇中文文章。關於他在網絡上發表的技術文章的一份清單,可以在 http://www.advogato.org/person/zhaoway/ 找到。他經常光顧南京大學小百合 BBS 上的 LinuxUnix 版。南大小百合的網址在 http://bbs.nju.edu.cn/ 趙蔚在南大小百合上面的 ID 是 iloveqhq 歡迎讀者朋友們和他討論 Linux/Free Software 相關的各種技術問題。
這就提示我們,如果要寫自己的防火牆模塊,鑲嵌在 netfilter 的架構中的話,我們主要要做的任務,就是向 netfilter 核心注冊 ipt_register_target() 或者 ipt_register_match()。
iptables 管理工具
最後,要說明的是 iptables,這個位於用戶空間的管理工具。前面我們看到了,netfilter 在內核空間的代碼根據 table 中的 rules,完成對 packet 的分析和處置。但是這些 table 中的具體的防火牆 rules,還是必須由系統管理員親自編寫。kernel 中的 netfilter 只是提供了一個機制,它並不知道該怎樣利用這個機制,寫出合適的 rules,來實現一個網絡防火牆。那麼,系統管理員編寫的 rules,怎樣進入位於 kernel 空間中的 netfilter 維護的 table 中去呢?
這個任務是由 iptables 這個工具來完成的。它經過 getsockopt() 以及 setsockopt() 兩個系統調用,進入 kernel 空間。這兩個調用是 BSD Socket 接口的一部分。這裡面的問題是 IPv4 在接到關於某個 sock 的不認識的 opt 的時候,應該怎麼處理?netfilter 要求它在 linux-2.4.19/net/ipv4/ip_sockglue.c 文件中處理 getsockopt() 和 setsockopt() 系統調用的 ip_sockopt() 函數中適當的地方調用 nf_sockopt()。這樣,用戶空間就可以和 netfilter 核心部分進行交流,可以維護 table 中的防火牆 rules 了。
小結
netfilter 對於 IPv4 的修改非常小,一是在若干個地方調用了 NF_HOOK(),二是在 ip_sockopt() 中調用了 nf_sockopt()。netfilter 的核心部分代碼只是維護 table,解釋 table 的任務在於其它的 kernel module。netfilter 會把從 hook “釣”起來的 packet 以及 table 裡面的相關內容發給注冊了的 module,決定 packet 的命運。
參考資料
1 netfilter/iptables 主站點在 http://www.netfilter.org/ 或者 http://www.iptables.org/ 在這個站點上,可以找到 netfilter 核心開發者 Paul Russell 寫的 Linux Packet Filtering HOW-TO,Linux NAT HOW-TO 等關於怎樣部署 Linux netfilter 防火牆的技術文章。
2 Linux 內核源代碼的在線交叉索引在 http://lxr.linux.no/ 這個站點可以幫助讀者朋友們更加方便的閱讀 Linux kernel 的源程序。
關於作者
趙蔚,住在南京市的 Linux/Free Software 獨立技術顧問。在 IBM developerWorks 發表過多篇中文文章。關於他在網絡上發表的技術文章的一份清單,可以在 http://www.advogato.org/person/zhaoway/ 找到。他經常光顧南京大學小百合 BBS 上的 LinuxUnix 版。南大小百合的網址在 http://bbs.nju.edu.cn/ 趙蔚在南大小百合上面的 ID 是 iloveqhq 歡迎讀者朋友們和他討論 Linux/Free Software 相關的各種技術問題。