netfilter中IP協(xié)議跟蹤和NAT實(shí)現(xiàn)
本文檔的Copyleft歸yfydz所有,使用GPL發(fā)布,可以自由拷貝,轉(zhuǎn)載,轉(zhuǎn)載時(shí)請(qǐng)保持文檔的完整性,嚴(yán)禁用于任何商業(yè)用途。
msn: [email]yfydz_no1@hotmail.com[/email]
來(lái)源:[url]http://yfydz.cublog.cn[/url]
1. 前言
和匹配和目標(biāo)一樣,netfilter提供了模塊化的IP層協(xié)議的跟蹤和NAT處理,除了內(nèi)核自帶的模塊外,用戶可以根據(jù)模塊格式自己編寫其他IP協(xié)議的跟蹤和NAT處理。注意:本文針對(duì)的跟蹤和NAT模塊是針對(duì)IP上層的協(xié)議,如TCP、UDP、ICMP等,而TCP、UDP上層的協(xié)議如FTP、TFTP等的跟蹤和NAT使用其他方式處理,將在以后的文章中介紹。
2. tuple
在具體介紹IP協(xié)議跟蹤前,需要說(shuō)明一個(gè)結(jié)構(gòu)ip_conntrack_tuple,這是netfilter用來(lái)描述跟蹤或NAT各IP協(xié)議時(shí)需要跟蹤或修改的各協(xié)議的信息,這些信息和連接的一一對(duì)應(yīng)的,對(duì)于所有IP協(xié)議,協(xié)議類型、源地址、目的地址這三個(gè)參數(shù)是識(shí)別連接所必須的,具體到各個(gè)協(xié)議,就要提取出各協(xié)議的唯一特征數(shù)據(jù),如TCP、UDP的源端口、目的端口,ICMP的ID、TYPE、CODE等值,這些值就是tuple結(jié)構(gòu)要處理的數(shù)據(jù)。各協(xié)議相關(guān)數(shù)據(jù)是以聯(lián)合形式定義在tuple結(jié)構(gòu)中的,netfilter缺省支持TCP、UDP和ICMP協(xié)議,如果還要支持其他IP協(xié)議,如GRE、ESP、AH、SCTP等,需要在聯(lián)合中添加相應(yīng)的協(xié)議參數(shù)值。
include/linux/netfilter_ipv4/ip_conntrack_tuple.h
/* The protocol-specific manipulable parts of the tuple: always in
network order! */
union ip_conntrack_manip_proto
{
/* Add other protocols here. */
u_int16_t all;
struct {
u_int16_t port;
} tcp;
struct {
u_int16_t port;
} udp;
struct {
u_int16_t id;
} icmp;
};
/* The manipulable part of the tuple. */
struct ip_conntrack_manip
{
u_int32_t ip;
union ip_conntrack_manip_proto u;
};
/* This contains the information to distinguish a connection. */
struct ip_conntrack_tuple
{
struct ip_conntrack_manip src;
/* These are the parts of the tuple which are fixed. */
struct {
u_int32_t ip;
union {
/* Add other protocols here. */
u_int16_t all;
struct {
u_int16_t port;
} tcp;
struct {
u_int16_t port;
} udp;
struct {
u_int8_t type, code;
} icmp;
} u;
/* The protocol. */
u_int16_t protonum;
} dst;
};
3. 協(xié)議連接跟蹤
netfilter中對(duì)每個(gè)要進(jìn)行跟蹤的IP協(xié)議定義了以下結(jié)構(gòu),每個(gè)IP協(xié)議的連接跟蹤處理就是要填寫這樣一個(gè)結(jié)構(gòu):
include/linux/netfilter_ipv4/ip_conntrack_protocol.h
struct ip_conntrack_protocol
{
/* Next pointer. */
struct list_head list;
/* Protocol number. */
u_int8_t proto;
/* Protocol name */
const char *name;
/* Try to fill in the third arg; return true if possible. */
int (*pkt_to_tuple)(const void *datah, size_t datalen,
struct ip_conntrack_tuple *tuple);
/* Invert the per-proto part of the tuple: ie. turn xmit into reply.
* Some packets can‘t be inverted: return 0 in that case.
*/
int (*invert_tuple)(struct ip_conntrack_tuple *inverse,
const struct ip_conntrack_tuple *orig);
/* Print out the per-protocol part of the tuple. */
unsigned int (*print_tuple)(char *buffer,
const struct ip_conntrack_tuple *);
/* Print out the private part of the conntrack. */
unsigned int (*print_conntrack)(char *buffer,
const struct ip_conntrack *);
/* Returns verdict for packet, or -1 for invalid. */
int (*packet)(struct ip_conntrack *conntrack,
struct iphdr *iph, size_t len,
enum ip_conntrack_info ctinfo);
/* Called when a new connection for this protocol found;
* returns TRUE if it‘s OK. If so, packet() called next. */
int (*new)(struct ip_conntrack *conntrack, struct iphdr *iph,
size_t len);
/* Called when a conntrack entry is destroyed */
void (*destroy)(struct ip_conntrack *conntrack);
/* Has to decide if a expectation matches one packet or not */
int (*exp_matches_pkt)(struct ip_conntrack_expect *exp,
struct sk_buff **pskb);
/* Module (if any) which this is connected to. */
struct module *me;
};
結(jié)構(gòu)中包括以下參數(shù):
struct list_head list:這是將該結(jié)構(gòu)掛接到協(xié)議跟蹤鏈表中的
u_int8_t proto:協(xié)議號(hào),在IP頭中的協(xié)議號(hào)是8位,1為ICMP,2為IGMP,6為TCP,17為UDP等等
const char *name:協(xié)議名稱,字符串常量
struct module *me:指向模塊本身,統(tǒng)計(jì)模塊是否被使用
結(jié)構(gòu)中包括以下函數(shù):
(*pkt_to_tuple):將數(shù)據(jù)包中的信息提取到tuple結(jié)構(gòu)中,如對(duì)于TCP/UDP,提取其端口值,在net/ipv4/netfilter/ip_conntrack_core.c的get_tuple()函數(shù)中調(diào)用;
(*invert_tuple):將tuple中數(shù)據(jù)進(jìn)行倒置,用來(lái)匹配處理連接的返回包,如對(duì)于TCP/UDP,要將端口值倒置,在net/ipv4/netfilter/ip_conntrack_core.c的invert_tuple()函數(shù)中調(diào)用;
(*print_tuple):打印協(xié)議相關(guān)的tuple值,在查看/proc/net/ip_conntrack文件時(shí)調(diào)用,net/ipv4/netfilter/ip_conntrack_standalone.c;
(*print_conntrack):打印協(xié)議相關(guān)的值,在查看/proc/net/ip_conntrack文件時(shí)調(diào)用,net/ipv4/netfilter/ip_conntrack_standalone.c;
(*packet):判斷數(shù)據(jù)包是否合法,并調(diào)整相應(yīng)連接的信息,也就是實(shí)現(xiàn)各協(xié)議的狀態(tài)檢測(cè),對(duì)于UDP等本身是無(wú)連接的協(xié)議的判斷比較簡(jiǎn)單,netfilter建立一個(gè)虛擬連接,每個(gè)新發(fā)包都是合法包,只等待回應(yīng)包到后連接都結(jié)束;但對(duì)于TCP之類的有狀態(tài)協(xié)議必須檢查數(shù)據(jù)是否符合協(xié)議的狀態(tài)轉(zhuǎn)換過(guò)程,這是靠一個(gè)狀態(tài)轉(zhuǎn)換數(shù)組實(shí)現(xiàn)的,在我以前的文章“什么是狀態(tài)檢測(cè)”中對(duì)這個(gè)數(shù)組進(jìn)行了描述。在net/ipv4/netfilter/ip_conntrack_core.c的ip_conntrack_in()函數(shù)中調(diào)用;
(*new):判斷是否是該協(xié)議的新連接,如對(duì)于TCP,必須用SYN包表示連接開始,而對(duì)于UDP和ICMP則始終是新連接,在net/ipv4/netfilter/ip_conntrack_core.c的init_conntrack()函數(shù)中調(diào)用;
(*destroy):在系統(tǒng)刪除連接時(shí)釋放該協(xié)議的特定數(shù)據(jù),不過(guò)目前都沒(méi)有使用,在net/ipv4/netfilter/ip_conntrack_core.c的destroy_conntrack()函數(shù)中調(diào)用;
(*exp_matches_pkt):判斷該數(shù)據(jù)包是否是期待的新包還是以前的重發(fā)包,只是在NAT處理時(shí)使用,針對(duì)的是有序列號(hào)控制的協(xié)議,如TCP,而無(wú)序列號(hào)控制的協(xié)議無(wú)此函數(shù)處理,在net/ipv4/netfilter/ip_nat_core.c的exp_for_packet()函數(shù)中調(diào)用;
最后,這些協(xié)議跟蹤結(jié)構(gòu)在net/ipv4/netfilter/ip_conntrack_core.c的
ip_conntrack_init()函數(shù)中掛接到協(xié)議跟蹤鏈表中:
list_append(&protocol_list, &ip_conntrack_protocol_tcp);
list_append(&protocol_list, &ip_conntrack_protocol_udp);
list_append(&protocol_list, &ip_conntrack_protocol_icmp);
要編寫自己的IP協(xié)議跟蹤模塊,先要分析這些協(xié)議頭中哪些信息可以用來(lái)唯一識(shí)別連接,作NAT時(shí)要修改哪些信息,把這些信息添加到ip_conntrack_tuple結(jié)構(gòu)的聯(lián)合中;然后填寫該協(xié)議的ip_conntrack_protocol結(jié)構(gòu),實(shí)現(xiàn)結(jié)構(gòu)中的內(nèi)部函數(shù);最后在ip_conntrack_init()函數(shù)中將此結(jié)構(gòu)掛接到協(xié)議跟蹤鏈表中。
4. 協(xié)議NAT
netfilter中對(duì)每個(gè)要進(jìn)行NAT的IP協(xié)議定義了以下結(jié)構(gòu),每個(gè)IP協(xié)議的NAT處理就是要填寫這樣一個(gè)結(jié)構(gòu):
include/linux/netfilter_ipv4/ip_nat_protocol.h
struct ip_nat_protocol
{
struct list_head list;
/* Protocol name */
const char *name;
/* Protocol number. */
unsigned int protonum;
/* Do a packet translation according to the ip_nat_proto_manip
* and manip type. */
void (*manip_pkt)(struct iphdr *iph, size_t len,
const struct ip_conntrack_manip *manip,
enum ip_nat_manip_type maniptype);
/* Is the manipable part of the tuple between min and max incl? */
int (*in_range)(const struct ip_conntrack_tuple *tuple,
enum ip_nat_manip_type maniptype,
const union ip_conntrack_manip_proto *min,
const union ip_conntrack_manip_proto *max);
/* Alter the per-proto part of the tuple (depending on
maniptype), to give a unique tuple in the given range if
possible; return false if not. Per-protocol part of tuple
is initialized to the incoming packet. */
int (*unique_tuple)(struct ip_conntrack_tuple *tuple,
const struct ip_nat_range *range,
enum ip_nat_manip_type maniptype,
const struct ip_conntrack *conntrack);
unsigned int (*print)(char *buffer,
const struct ip_conntrack_tuple *match,
const struct ip_conntrack_tuple *mask);
unsigned int (*print_range)(char *buffer,
const struct ip_nat_range *range);
};
結(jié)構(gòu)中包括以下參數(shù):
struct list_head list:這是將該結(jié)構(gòu)掛接到協(xié)議跟蹤鏈表中的
const char *name:協(xié)議名稱,字符串常量
unsigned int protonum:協(xié)議號(hào),在IP頭中的協(xié)議號(hào)是8位,在此用unsigned int有點(diǎn)浪費(fèi)
結(jié)構(gòu)中包括以下函數(shù):
(*manip_pkt):修改協(xié)議相關(guān)數(shù)據(jù),根據(jù)NAT規(guī)則來(lái)確定是修改源部分還是目的部分,在net/ipv4/netfilter/ip_nat_core.c的manip_pkt()函數(shù)中調(diào)用;
(*in_range):判斷數(shù)據(jù)包是否是要進(jìn)行NAT修改,在net/ipv4/netfilter/ip_nat_core.c的in_range()、get_unique_tuple()等函數(shù)中調(diào)用;
(*unique_tuple):構(gòu)造一個(gè)新tuple處理將原tuple在進(jìn)行NAT后對(duì)應(yīng)的連接參數(shù),如TCP源NAT時(shí),除了源地址必須要修改外,一般還要修改源端口,這個(gè)連接的后續(xù)包的源端口就都改這個(gè)端口值,而修改后的這個(gè)端口值必須是唯一的,和這個(gè)連接綁定,其他連接就不能再使用這個(gè)端口,如果找不到合適的tuple值,NAT將失敗,也就是說(shuō),對(duì)于多對(duì)一的NAT轉(zhuǎn)換,理論上最多只能處理65535個(gè)TCP連接,超過(guò)此數(shù)的新的TCP連接就無(wú)法進(jìn)行NAT了,對(duì)于TCP、UDP,(*unique_tuple)就是檢測(cè)查找一個(gè)新的未用端口生成一個(gè)新的tuple結(jié)構(gòu)對(duì)應(yīng)該連接,對(duì)應(yīng)ICMP,則是找一個(gè)未用的ID值,該函數(shù)在net/ipv4/netfilter/ip_nat_core.c的get_unique_tuple()函數(shù)中調(diào)用;
(*print):打印struct ip_conntrack_tuple中的協(xié)議相關(guān)信息;
(*print_range):打印struct ip_nat_range結(jié)構(gòu)中要進(jìn)行NAT修改的那部分協(xié)議信息;
最后,這些協(xié)議跟蹤結(jié)構(gòu)在net/ipv4/netfilter/ip_nat_core.c的ip_nat_init()函數(shù)中掛接到協(xié)議NAT鏈表中:
list_append(&protos, &ip_nat_protocol_tcp);
list_append(&protos, &ip_nat_protocol_udp);
list_append(&protos, &ip_nat_protocol_icmp);
對(duì)新IP協(xié)議的NAT模塊的添加和跟蹤模塊的添加類似。
5. 其他IP協(xié)議的跟蹤和NAT
下面討論其他IP協(xié)議如果要進(jìn)行跟蹤和NAT要處理哪些協(xié)議相關(guān)數(shù)據(jù):
SCTP:RFC2960,協(xié)議號(hào)132,和TCP非常類似,用源端口和目的端口來(lái)識(shí)別;
SCTP Common Header Format
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port Number | Destination Port Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Verification Tag |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
IGMP:RFC3376,協(xié)議號(hào)2,IGMP頭內(nèi)信息太少,沒(méi)有特殊數(shù)據(jù)供識(shí)別
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Max Resp Time | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Group Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
GRE:RFC1701,RFC2784,協(xié)議號(hào)47,使用KEY來(lái)作為修改數(shù)據(jù),ver和protocol作為識(shí)別用的固定數(shù)據(jù)
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|C|R|K|S|s|Recur| Flags | Ver | Protocol Type |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum (optional) | Offset (optional) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Key (optional) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number (optional) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Routing (optional) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|C| Reserved0 | Ver | Protocol Type |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum (optional) | Reserved1 (Optional) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
ESP:RFC4303,協(xié)議號(hào)50,只能用SPI來(lái)識(shí)別,SPI是SA的一部分,不過(guò)是不能修改的,因?yàn)镾PI是在IKE協(xié)商過(guò)程中確定的,兩邊都已經(jīng)預(yù)先知道,一旦修改了就匹配不到SA了
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Security Parameters Index (SPI) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Payload Data* (variable) |
| |
| |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | Padding (0-255 bytes) |
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | Pad Length | Next Header |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Integrity Check Value-ICV (variable) |
~ ~
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
AH:RFC4304,協(xié)議號(hào)51,AH協(xié)議無(wú)法進(jìn)行NAT的,否則認(rèn)證就會(huì)失敗,跟蹤也只能靠SPI
6. 結(jié)論
netfilter的IP協(xié)議跟蹤和NAT處理很好地實(shí)現(xiàn)了模塊化,但除了netfilter自帶的模塊外,可處理其他IP協(xié)議也已經(jīng)不多了,只有GRE和SCTP可以新增模塊,其他協(xié)議增加模塊基本無(wú)意義。