一、Netfilter和Iptables概述
netfilter/iptables IP 資訊包過濾系統是一種功能強大的工具,可用於添加、編輯和除去規則,這些規則是在做資訊包過濾決定時,防火牆所遵循和組成的規則。這些規則儲存在專用的資訊包過濾表中,而這些表整合在 Linux 核心中。在資訊包過濾表中,規則被分組放在我們所謂的鏈(chain)中。
雖然 netfilter/iptables IP 資訊包過濾系統被稱為單個實體,但它實際上由兩個組件 netfilter 和 iptables 組成。
netfilter 組件也稱為核心空間(kernelspace),是核心的一部分,由一些資訊包過濾表組成,這些表包含核心用來控制資訊包過濾處理的規則集。
iptables 組件是一種工具,也稱為使用者空間(userspace),它使插入、修改和除去資訊包過濾表中的規則變得容易。
iptables包含4個表,5個鏈。其中表是按照對資料包的操作區分的,鏈是按照不同的Hook點來區分的,表和鏈實際上是netfilter的兩個維度。
4個表:filter,nat,mangle,raw,預設表是filter(沒有指定表的時候就是filter表)。表的處理優先順序:raw>mangle>nat>filter。
filter:一般的過濾功能
nat:用於nat功能(連接埠映射,地址映射等)
mangle:用於對特定資料包的修改
raw:有限級最高,設定raw時一般是為了不再讓iptables做資料包的連結跟蹤處理,提高效能
5個鏈:PREROUTING,INPUT,FORWARD,OUTPUT,POSTROUTING。
PREROUTING:資料包進入路由表之前
INPUT:通過路由表後目的地為本機
FORWARDING:通過路由表後,目的地不為本機
OUTPUT:由本機產生,向外轉寄
POSTROUTIONG:發送到網卡介面之前。如:
iptables中表和鏈的對應關係如下:
資料包在netfilter中的傳遞流程:
二、Iptables的使用:
iptables [-t 表名] -命令 –鏈表 [-匹配] [-j 動作/目標]
表名:filter;nat;mangle;raw
常用命令:
-A, --append chain rule-specification:添加
-D, --delete chain rule-specification:刪除
-D, --delete chain rulenum:刪除
-I, --insert chain [rulenum] rule-specification:插入
-R, --replace chain rulenum rule-specification:替換
-L, --list [chain]:顯示
-F, --flush [chain]:重新整理
鏈表:INPUT;OUTPUT;FORWORD;PREROUTING;POSTROUTING
匹配:
-p -protocal [!]protocol:協議
-s -source [!] address[/mask]:源地址
-d --destination [!] address[/mask]:目的地址
-j --jump target:
-i –in-interface [!][name]:入口
-o --out-interface [!][name]:出口
-f, --fragment:分區
匹配擴充:
指定-p tcp時:
--source-port [!] [port[:port]]:原連接埠(也作--sport)
--destionation-port [!] [port:[port]]:目的連接埠(也作--dport)
--tcp-flags [!] mask comp:匹配指定的TCP標記
[!] –syn:設定了SYN位而清除了ACK和FIN位的TCP包。
--tcp-option [!] number:設定了TCP選項的包
指定 –p udp時:
--source-port [!] [port:[port]]
--destination-port [!] [port:[port]]
常見動作:
ACCEPT:允許存取
DROP:拒絕
QUEUE:傳遞給應用程式層
REJECT:和DROP類似,只是REJECT還返回一個錯誤包訊息
REDIRECT:在nat表PREROUTING鏈中使用,修改資料包為本機地址和使用者指定的連接埠。
TPROXY:在mangle表的PREROUTING鏈中使用,不修改資料包包頭,直接把資料傳遞給一個本地socket。
較完整的命令參數見:
三、NetFilter的核心實現
Netfilter中有兩個要點:一個是Hooks點的實現,一個是表、鏈、規則的概念
Hooks
struct list_head nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS]是其中核心的資料結構。nf_hooks的功能類似一個二維的函數指標數組。
nf_hooks數組的第一維是按照協議進行分類的,對於不同的協議有不同的hook點和hook函數,常見的協議包括ipv4,ipv6,arp,bridge等。
nf_hooks數組的第二維是按照hook點進行劃分的,分為
NF_INET_PRE_ROUTING,NF_INET_LOCAL_IN,NF_INET_FORWARD,NF_INET_LOCAL_OUT,NF_INET_POST_ROUTING等5個hook點,與iptables的5個鏈相對應。
nf_hooks數組中的每一個元素可以理解為一個函數指標鏈表的鏈表頭。這個函數指標鏈表是一個有序鏈表,按照函數hook的優先順序進行排序。iptables的4個表分別對應不同的優先順序:NF_IP_PRI_RAW、NF_IP_PRI_MANGLE、NF_IP_PRI_NAT_DST /NF_IP_PRI_NAT_SRC、NF_IP_PRI_LAST等。iptables中的表的優先順序就是通過有序鏈表的方式來實現的。
鏈表元素的實際的資料結構是struct nf_hook_ops,其核心的資料成員就是一
個函數指標,還包含其他的一些屬性。
struct nf_hook_ops
{
struct list_head list; //鏈表成員
/* User fills in from here down. */
nf_hookfn *hook; //鉤子函數指標
struct module *owner;
int pf; //協議簇,對於ipv4而言,是PF_INET
int hooknum; //hook類型
int priority; //優先順序
};
nf_hooks的全域結構如下:
netfilter中的資料流程如:
為ipv4協議的資料包,當資料包到達對應的hook點時,檢查對應的nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS]函數指標鏈表是否為空白,如果為空白則繼續tcp/ip協議棧的後續流程;如果不為空白,則按照鏈表中的順序依次調用hook函數。
iptables的預設的核心hook函數是ipt_do_table(ip_tables.c),其核心功能是檢查對應的協議的對應的表中的對應的鏈中的規則是否和當前的資料包相匹配,如果匹配則執行對應的動作。
Tabales
netfilter中提供了一系列的表(tables),每個表包含若干個鏈(chains),而每條鏈中包含由一條或若干條規則(rules),每一條規則都被用於資料包的檢測。實際上netfilter是表的容器,表是鏈的容器,而鏈又是規則的容器。
相關的資料結構:
struct xt_table //表結構
{
struct list_head list;//錶鏈
unsigned int valid_hooks;
struct xt_table_info *private;// iptable的資料區
struct module *me;
u_int8_t af; //協議簇
int priority; //優先順序
const char name[XT_TABLE_MAXNAMELEN];// 表名,如"filter"、"nat"等,為了滿足自動模組載入的設計,包含該表的模組應命名為iptable_'name'.o
};
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;//規則表入口
};
下面的3種資料額結構是被填充到struct ipt_table_info的規則表(entries開始的)中的。
ipt_entry結構如所示,其成員ip指向結構ipt_ip,該結構主要儲存規則中標準匹配的內容 (IP、mask、interface、proto等),target_offset的值等於ipt_entry的長度與 ipt_entry_matches的長度之和,next_offset的值等於規則中三個部分的長度之和。通過target_offset與 next_offset可以實現規則的遍曆。
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的起始位置 */
}
ipt_entry_match主要儲存規則中擴充匹配內容(tos、ttl、time等),其是 Netfilter中核心與使用者態互動的關鍵資料結構,在其核心部分由一個函數指標指向一個ipt_match結構,該結構體中包含了對包做匹配的函數, 是真正對包做匹配的地方。ipt_entry_target結構與ipt_entry_match結構很類似。
struct xt_entry_
union {
struct {
__u16 match_size;
char name[XT_EXTENSION_MAXNAMELEN];
__u8 revision;
} user;
struct {
__u16 match_size;
struct xt_match *match;
} kernel;
__u16 match_size;
} u;
unsigned char data[0];
};
struct xt_entry_target {
union {
struct {
__u16 target_size;
char name[XT_EXTENSION_MAXNAMELEN];
__u8 revision;
} user;
struct {
__u16 target_size;
struct xt_target *target;
} kernel;
__u16 target_size;
} u;
unsigned char data[0];
};
上面各種資料結構是按照的形式進行關聯的。這裡被分成表、鏈、規則三級。
每個表(xt_tables)都有一個private指標指向一個ipt_table_info結構,在ipt_table_info結構之後緊接著的是是實際的規則。
從資料結構中看鏈結構不是很明顯,但是規則是被封裝在鏈中的。ipt_table_info結構中的hook_entry成員和underflow成員就是用於劃分規則所在的鏈。
每個規則由一個ipt_entry結構,N個xt_entry_match結構和一個xt_entry_target結構組成。ipt_entry結構表示的是基本的匹配規則(協議、地址等),xt_entry_match結構表示的是擴充的匹配規則,xt_entry_target結構表示的是匹配之後的動作。
四、NetFilter的利用
由於netfilter在核心中預留了Hook點,因此可以通過載入模組的方式方便對其加以利用。下面代碼可以在2.6.20上運行。
//nethook.c<br />#include <linux/module.h><br />#include <linux/kernel.h><br />#include <linux/netfilter.h><br />#include <linux/netfilter_ipv4.h><br />#include <linux/netdevice.h><br />#include <linux/skbuff.h><br />#include <linux/ip.h><br />#include <linux/tcp.h></p><p>static struct nf_hook_ops nfho;<br />unsigned int hook_func(unsigned int hooknum,<br /> struct sk_buff **skb,<br /> const struct net_device *in,<br /> const struct net_device *out,<br /> int (*okfn)(struct sk_buff *))<br />{<br />#ifdef BASE_TEST<br /> return NF_DROP;<br />#endif<br />#ifdef INTF_TEST<br /> if(strcmp(in->name,"eth0") == 0){<br /> return NF_DROP;<br /> }<br />#endif<br />#ifdef ADDR_TEST<br /> static unsigned char *drop_ip = "/x0a/x08/x50/x6c";<br /> struct sk_buff *sk = *skb;<br /> if(sk->nh.iph->saddr == *(unsigned int *)drop_ip){<br /> return NF_DROP;<br /> }<br />#endif</p><p>#ifdef PORT_TEST<br /> unsigned char *deny_port = "/x00/x19"; /* port 25 */<br /> struct tcphdr *thead;<br /> if (!skb )<br /> return NF_ACCEPT;<br /> if (!(skb->nh.iph))<br /> return NF_ACCEPT;</p><p> if (skb->nh.iph->protocol != IPPROTO_TCP) {<br /> return NF_ACCEPT;<br /> }<br /> thead = (struct tcphdr *)(skb->data +(skb->nh.iph->ihl * 4));</p><p> if ((thead->dest) == *(unsigned short *)deny_port) {<br /> return NF_DROP;<br /> }<br />#endif<br /> return NF_ACCEPT;<br />}</p><p>static int __init init_nethook(void)<br />{<br /> nfho.hook = hook_func;<br /> nfho.hooknum = NF_IP_PRE_ROUTING;<br /> nfho.pf = PF_INET;<br /> nfho.priority = NF_IP_PRI_FIRST;<br /> nf_register_hook(&nfho);<br /> return 0;<br />}</p><p>static void __exit exit_nethook(void)<br />{<br /> nf_unregister_hook(&nfho);<br />}</p><p>module_init(init_nethook);<br />module_exit(exit_nethook);<br />
Makefile:
obj-m = nethook.o<br />KVERSION = $(shell uname -r)<br />all:<br />make -C /lib/modules/$(KVERSION)/build M=$(PWD) modules<br />clean:<br />make -C /lib/modules/$(KVERSION)/build M=$(PWD) clean<br />
利用上述模組可以簡單實現對地址、連接埠的過濾