Linux Netfilter實現機制和擴充技術之二(Netfilter Frame)

來源:互聯網
上載者:User

Netfilter是2.4.x核心引入的,儘管它提供了對2.0.x核心中的ipfw以及2.2.x核心中的ipchains的相容,但實際上它
的工作和意義遠不止於此。從上面對IP報文的流程分析中可以看出,Netfilter和IP報文的處理是完全結合在一起的,同時由於其結構相對獨立,又是
可以完全剝離的。這種機制也是Netfilter-iptables既高效又靈活的保證之一。

在剖析Netfilter機制之前,我們還是由淺入深的從Netfilter的使用開始。

2.1 編譯

在Networking Options中選定Network packet filtering項,並將其下的IP:Netfilter
Configurations小節的所有選項設為Module模式。編譯並安裝新核心,然後重啟,系統的核內Netfilter就配置好了。以下對相關的
核心配置選項稍作解釋,也可以參閱編譯系統內建的Help:

【Kernel/User netlink socket】建立一類PF_NETLINK通訊端族,用於核心與使用者進程通訊。當Netfilter需要使用使用者隊列來管理某些報文時就要使用這一機制;

【Network packet filtering (replaces ipchains)】Netfilter主選項,提供Netfilter架構;

【Network packet filtering debugging】Netfilter主選項的分支,支援更詳細的Netfilter報告;

【IP: Netfilter Configuration】此節下是netfilter的各種選項的集合:

【Connection tracking (required for masq/NAT)】串連跟蹤,用於基於串連的報文處理,比如NAT;

【IP tables support (required for filtering/masq/NAT)】這是Netfilter的架構,NAT等應用的容器;

【ipchains (2.2-style) support】ipchains機制的相容代碼,在新的Netfilter結構上實現了ipchains介面;

【ipfwadm (2.0-style) support】2.0核心防火牆ipfwadm相容代碼,基於新的Netfilter實現。

2.2 總體結構

Netfilter是嵌入核心IP協議棧的一系列調用入口,設定在報文處理的路徑上。網路報文按照來源和去向,可以分為三類:流入的、流經的和流出
的,其中流入和流經的報文需要經過路由才能區分,而流經和流出的報文則需要經過投遞,此外,流經的報文還有一個FORWARD的過程,即從一個NIC轉到
另一個NIC。Netfilter就是根據網路報文的流向,在以下幾個點插入處理過程:

NF_IP_PRE_ROUTING,在報文作路由以前執行;

NF_IP_FORWARD,在報文轉向另一個NIC以前執行;

NF_IP_POST_ROUTING,在報文流出以前執行;

NF_IP_LOCAL_IN,在流入本地的報文作路由以後執行;

NF_IP_LOCAL_OUT,在本地報文做流出路由前執行。

圖5 Netfilter HOOK位置

Netfilter架構為多種協議提供了一套類似的鉤子(HOOK),用一個struct list_head
nf_hooks[NPROTO][NF_MAX_HOOKS]二維數組結構儲存,一維為協議族,二維為上面提到的各個調用入口。每個希望嵌入
Netfilter中的模組都可以為多個協議族的多個調用點註冊多個鉤子函數(HOOK),這些鉤子函數將形成一條函數指標鏈,每次協議棧代碼執行到
NF_HOOK()函數時(有多個時機),都會依次啟動所有這些函數,處理參數所指定的協議棧內容。

每個註冊的鉤子函數經過處理後都將返回下列值之一,告知Netfilter核心代碼處理結果,以便對報文採取相應的動作:

NF_ACCEPT:繼續正常的報文處理;

NF_DROP:將報文丟棄;

NF_STOLEN:由鉤子函數處理了該報文,不要再繼續傳送;

NF_QUEUE:將報文入隊,通常交由使用者程式處理;

NF_REPEAT:再次調用該鉤子函數。

2.3 IPTables

Netfilter-iptables由兩部分組成,一部分是Netfilter的"鉤子",另一部分則是知道這些鉤子函數如何工作的一套規則--
這些規則儲存在被稱為iptables的資料結構之中。鉤子函數通過訪問iptables來判斷應該返回什麼值給Netfilter架構。

在現有(kernel 2.4.21)中已內建了三個iptables:filter、nat和mangle,絕大部分報文處理功能都可以通過在這些內建(built-in)的表格中填入規則完成:

filter,該模組的功能是過濾報文,不作任何修改,或者接受,或者拒絕。它在NF_IP_LOCAL_IN、NF_IP_FORWARD和NF_IP_LOCAL_OUT三處註冊了鉤子函數,也就是說,所有報文都將經過filter模組的處理。

nat,網路位址轉譯(Network Address Translation),該模組以Connection
Tracking模組為基礎,僅對每個串連的第一個報文進行匹配和處理,然後交由Connection
Tracking模組將處理結果應用到該串連之後的所有報文。nat在NF_IP_PRE_ROUTING、NF_IP_POST_ROUTING註冊了
鉤子函數,如果需要,還可以在NF_IP_LOCAL_IN和NF_IP_LOCAL_OUT兩處註冊鉤子,提供對本地報文(出/入)的地址轉換。nat
僅對報文頭的地址資訊進行修改,而不修改報文內容,按所修改的部分,nat可分為源NAT(SNAT)和目的NAT(DNAT)兩類,前者修改第一個報文
的源地址部分,而後者則修改第一個報文的目的地址部分。SNAT可用來實現IP偽裝,而DNAT則是透明代理的實現基礎。

mangle,屬於可以進行報文內容修改的IP Tables,可供修改的報文內容包括MARK、TOS、TTL等,mangle表的操作函數嵌入在Netfilter的NF_IP_PRE_ROUTING和NF_IP_LOCAL_OUT兩處。

核心編程人員還可以通過注入模組,調用Netfilter的介面函數建立新的iptables。在下面的Netfilter-iptables應用中我們將進一步接觸Netfilter的結構和使用方式。

2.4 Netfilter組態工具

iptables是專門針對2.4.x核心的Netfilter製作的核外組態工具,通過socket介面對Netfilter進行操作,建立socket的方式如下:

socket(TC_AF, SOCK_RAW, IPPROTO_RAW)

其中TC_AF就是AF_INET。核外程式可以通過建立一個"原始IP通訊端"獲得訪問Netfilter的控制代碼,然後通過getsockopt()和setsockopt()系統調用來讀取、更改Netfilter設定,詳情見下。

iptables功能強大,可以對核內的表進行操作,這些操作主要指對其中規則鏈的添加、修改、清除,它的命令列參數主要可分為四類:指定所操作的
IP
Tables(-t);指定對該表所進行的操作(-A、-D等);規則描述和匹配;對iptables命令本身的指令(-n等)。在下面的例子中,我們通
過iptables將訪問10.0.0.1的53連接埠(DNS)的TCP串連引導到192.168.0.1地址上。

iptables -t nat -A PREROUTING -p TCP -i eth0 -d 10.0.0.1 --dport 53 -j DNAT --to-destination 192.168.0.1

由於iptables是操作核內Netfilter的使用者介面,有時也把Netfilter-iptables簡稱為iptables,以便與ipchains、ipfwadm等老版本的防火牆並列。

2.5 iptables核心資料結構

2.5.1 表

在Linux核心裡,iptables用struct ipt_table表示,定義如下(include/linux/netfilter_ipv4/ip_tables.h):


struct ipt_table
{
struct list_head list;
/* 錶鏈 */
char name[IPT_TABLE_MAXNAMELEN];
/* 表名,如"filter"、"nat"等,為了滿足自動模組載入的設計,包含該表的模組應命名為iptable_'name'.o */
struct ipt_replace *table;
/* 表模子,初始為initial_table.repl */
unsigned int valid_hooks;
/* 位向量,標示本表所影響的HOOK */
rwlock_t lock;
/* 讀寫鎖,初始為開啟狀態 */
struct ipt_table_info *private;
/* iptable的資料區,見下 */
struct module *me;
/* 是否在模組中定義 */
};
struct ipt_table_info是實際描述表的資料結構(net/ipv4/netfilter/ip_tables.c):
struct ipt_table_info
{
unsigned int size;
/* 表大小 */
unsigned int number;
/* 表中的規則數 */
unsigned int initial_entries;
/* 初始的規則數,用於模組計數 */
unsigned int hook_entry[NF_IP_NUMHOOKS];
/* 記錄所影響的HOOK的規則入口相對於下面的entries變數的位移量 */
unsigned int underflow[NF_IP_NUMHOOKS];
/* 與hook_entry相對應的規則表上限位移量,當無規則錄入時,相應的hook_entry和underflow均為0 */
char entries[0] ____cacheline_aligned;
/* 規則表入口 */
};

 

例如內建的filter表初始定義如下(net/ipv4/netfilter/iptable_filter.c):


static struct ipt_table packet_filter
= { { NULL, NULL },// 鏈表
"filter",// 表名
&initial_table.repl,// 初始的表模板
FILTER_VALID_HOOKS,// 定義為((1 << NF_IP6_LOCAL_IN) | (1 << NF_IP6_FORWARD) | (1 << NF_IP6_LOCAL_OUT)),
即關心INPUT、FORWARD、OUTPUT三點
RW_LOCK_UNLOCKED,// 鎖
NULL,// 初始的表資料為空白
THIS_MODULE // 模組標示
};

 

經過調用ipt_register_table(&packet_filter)後,filter表的private資料區即參照模板填好了。

2.5.2 規則

規則用struct ipt_entry結構表示,包含匹配用的IP頭部分、一個Target和0個或多個Match。由於Match數不定,所以一條規則實際的佔用空間是可變的。結構定義如下(include/linux/netfilter_ipv4):


struct ipt_entry
{
struct ipt_ip ip;
/* 所要匹配的報文的IP頭資訊 */
unsigned int nfcache;
/* 位向量,標示本規則關心報文的什麼部分,暫未使用 */
u_int16_t target_offset;
/* target區的位移,通常target區位於match區之後,而match區則在ipt_entry的末尾;
初始化為sizeof(struct ipt_entry),即假定沒有match */
u_int16_t next_offset;
/* 下一條規則相對於本規則的位移,也即本規則所用空間的總和,
初始化為sizeof(struct ipt_entry)+sizeof(struct ipt_target),即沒有match */
unsigned int comefrom;
/* 位向量,標記調用本規則的HOOK號,可用於檢查規則的有效性 */
struct ipt_counters counters;
/* 記錄該規則處理過的報文數和報文總位元組數 */
unsigned char elems[0];
/*target或者是match的起始位置 */
}

 

規則按照所關注的HOOK點,被放置在struct ipt_table::private->entries之後的地區,比鄰排列。

2.5.3 規則填寫過程

在瞭解了iptables在核心中的資料結構之後,我們再通過遍曆一次使用者通過iptables配置程式填寫規則的過程,來瞭解這些資料結構是如何工作的了。

一個最簡單的規則可以描述為拒絕所有轉寄報文,用iptables命令表示就是:


iptables -A FORWARD -j DROP;

 

iptables應用程式將命令列輸入轉換為程式可讀的格式(iptables-
standalone.c::main()::do_command(),然後再調用libiptc庫提供的iptc_commit()函數向核心提交該
操作請求。在libiptc/libiptc.c中定義了iptc_commit()(即TC_COMMIT()),它根據請求設定了一個struct
ipt_replace結構,用來描述規則所涉及的表(filter)和HOOK點(FORWARD)等資訊,並在其後附接當前這條規則--一個
struct
ipt_entry結構(實際上也可以是多個規則entry)。組織好這些資料後,iptc_commit()調用setsockopt()系統調用來啟
動核心處理這一請求:


setsockopt(
sockfd,//通過socket(TC_AF, SOCK_RAW, IPPROTO_RAW)建立的通訊端,其中TC_AF即AF_INET
TC_IPPROTO,//即IPPROTO_IP
SO_SET_REPLACE, //即IPT_SO_SET_REPLACE
repl,//struct ipt_replace結構
sizeof(*repl) + (*handle)->entries.size)//ipt_replace加上後面的ipt_entry

 

核心對於setsockopt()的處理是從協議棧中一層層傳遞上來的,調用過程如所示:

圖6 規則填寫過程

nf_sockopts是在iptables進行初始化時通過nf_register_sockopt()函數產生的一個struct
nf_sockopt_ops結構,對於ipv4來說,在net/ipv4/netfilter/ip_tables.c中定義了一個
ipt_sockopts變數(struct
nf_sockopt_ops),其中的set操作指定為do_ipt_set_ctl(),因此,當nf_sockopt()調用對應的set操作時,
控制將轉入net/ipv4/netfilter/ip_tables.c::do_ipt_set_ctl()中。

對於IPT_SO_SET_REPLACE命令,do_ipt_set_ctl()調用do_replace()來處理,該函數將使用者層傳入的
struct ipt_replace和struct ipt_entry組織到filter(根據struct
ipt_replace::name項)表的hook_entry[NF_IP_FORWARD]所指向的地區,如果是添加規則,結果將是filter表
的private(struct
ipt_table_info)項的hook_entry[NF_IP_FORWARD]和underflow[NF_IP_FORWARD]的差值擴大
(用於容納該規則),private->number加1。

2.5.4 規則應用過程

以上描述了規則注入核內iptables的過程,這些規則都掛接在各自的表的相應HOOK入口處,當報文流經該HOOK時進行匹配,對於與規則匹配
成功的報文,調用規則對應的Target來處理。仍以轉寄的報文為例,假定filter表中添加了如上所述的規則:拒絕所有轉寄報文。

如1.2節所示,經由本地轉寄的報文經過路由以後將調用ip_forward()來處理,在ip_forward()返回前,將調用如下代碼:


NF_HOOK(PF_INET, NF_IP_FORWARD, skb, skb->dev, dev2, ip_forward_finish)
NF_HOOK是這樣一個宏(include/linux/netfilter.h):
#define NF_HOOK(pf, hook, skb, indev, outdev, okfn)/
(list_empty(&nf_hooks[(pf)][(hook)])/
? (okfn)(skb)/
: nf_hook_slow((pf), (hook), (skb), (indev), (outdev), (okfn)))

 

也就是說,如果nf_hooks[PF_INET][NF_IP_FORWARD]所指向的鏈表為空白(即該鉤子上沒有掛處理函數),則直接調用
ip_forward_finish(skb)完成ip_forward()的操作;否則,則調用net/core
/netfilter.c::nf_hook_slow()轉入Netfilter的處理。

這裡引入了一個nf_hooks鏈表二維數組:


struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS];

 

每一個希望使用Netfilter掛鈎的表都需要將表處理函數在nf_hooks數組的相應鏈表上進行註冊。對於filter表來說,在其初始化
(net/ipv4/netfilter/iptable_filter.c::init())時,調用了net/core
/netfilter.c::nf_register_hook(),將預定義的三個struct
nf_hook_ops結構(分別對應INPUT、FORWARD、OUTPUT鏈)連入鏈表中:


struct nf_hook_ops
{
struct list_head list;
//鏈表
nf_hookfn *hook;
//處理函數指標
int pf;
//協議號
int hooknum;
//HOOK號
int priority;
//優先順序,在nf_hooks鏈表中各處理函數按優先順序排序
};

 

對於filter表來說,FORWARD點的hook設定成ipt_hook(),它將直接調用ipt_do_table()。幾乎所有處理函數最
終都將調用ipt_do_table()來查詢表中的規則,以調用對應的target。所示即為在FORWARD點上調用
nf_hook_slow()的過程:

圖7 規則應用流程

2.5.5 Netfilter的結構特點

由上可見,nf_hooks鏈表數組是聯絡報文處理流程和iptables的紐帶,在iptables初始化(各自的init()函數)時,一方面
調用nf_register_table()建立規則容器,另一方面還要調用nf_register_hook()將自己的掛鈎願望表達給
Netfilter架構。初始化完成之後,使用者只需要通過使用者級的iptables命令操作規則容器(添加規則、刪除規則、修改規則等),而對規則的使用
則完全不用操心。如果一個容器內沒有規則,或者nf_hooks上沒有需要表達的願望,則報文處理照常進行,絲毫不受Netfilter-
iptables的影響;即使報文經過了過濾規則的處理,它也會如同平時一樣重新回到報文處理流程上來,因此從宏觀上看,就像在行車過程中去了一趟加油站。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.