netfilter中IP協(xié)議跟蹤和NAT實(shí)現(xiàn)
本文檔的Copyleft歸yfydz所有,使用GPL發(fā)布,可以自由拷貝,轉(zhuǎn)載,轉(zhuǎn)載時請保持文檔的完整性,嚴(yán)禁用于任何商業(yè)用途。 msn: [email]yfydz_no1@hotmail.com[/email] 來源:[url]http://yfydz.[/url] 1. 前言 和匹配和目標(biāo)一樣,netfilter提供了模塊化的IP層協(xié)議的跟蹤和NAT處理,除了內(nèi)核自帶的模塊外,用戶可以根據(jù)模塊格式自己編寫其他IP協(xié)議的跟蹤和NAT處理。注意:本文針對的跟蹤和NAT模塊是針對IP上層的協(xié)議,如TCP、UDP、ICMP等,而TCP、UDP上層的協(xié)議如FTP、TFTP等的跟蹤和NAT使用其他方式處理,將在以后的文章中介紹。 2. tuple 在具體介紹IP協(xié)議跟蹤前,需要說明一個結(jié)構(gòu)ip_conntrack_tuple,這是netfilter用來描述跟蹤或NAT各IP協(xié)議時需要跟蹤或修改的各協(xié)議的信息,這些信息和連接的一一對應(yīng)的,對于所有IP協(xié)議,協(xié)議類型、源地址、目的地址這三個參數(shù)是識別連接所必須的,具體到各個協(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中對每個要進(jìn)行跟蹤的IP協(xié)議定義了以下結(jié)構(gòu),每個IP協(xié)議的連接跟蹤處理就是要填寫這樣一個結(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é)議號,在IP頭中的協(xié)議號是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)中,如對于TCP/UDP,提取其端口值,在net/ipv4/netfilter/ip_conntrack_core.c的get_tuple()函數(shù)中調(diào)用; (*invert_tuple):將tuple中數(shù)據(jù)進(jìn)行倒置,用來匹配處理連接的返回包,如對于TCP/UDP,要將端口值倒置,在net/ipv4/netfilter/ip_conntrack_core.c的invert_tuple()函數(shù)中調(diào)用; (*print_tuple):打印協(xié)議相關(guān)的tuple值,在查看/proc/net/ip_conntrack文件時調(diào)用,net/ipv4/netfilter/ip_conntrack_standalone.c; (*print_conntrack):打印協(xié)議相關(guān)的值,在查看/proc/net/ip_conntrack文件時調(diào)用,net/ipv4/netfilter/ip_conntrack_standalone.c; (*packet):判斷數(shù)據(jù)包是否合法,并調(diào)整相應(yīng)連接的信息,也就是實(shí)現(xiàn)各協(xié)議的狀態(tài)檢測,對于UDP等本身是無連接的協(xié)議的判斷比較簡單,netfilter建立一個虛擬連接,每個新發(fā)包都是合法包,只等待回應(yīng)包到后連接都結(jié)束;但對于TCP之類的有狀態(tài)協(xié)議必須檢查數(shù)據(jù)是否符合協(xié)議的狀態(tài)轉(zhuǎn)換過程,這是靠一個狀態(tài)轉(zhuǎn)換數(shù)組實(shí)現(xiàn)的,在我以前的文章“什么是狀態(tài)檢測”中對這個數(shù)組進(jìn)行了描述。在net/ipv4/netfilter/ip_conntrack_core.c的ip_conntrack_in()函數(shù)中調(diào)用; (*new):判斷是否是該協(xié)議的新連接,如對于TCP,必須用SYN包表示連接開始,而對于UDP和ICMP則始終是新連接,在net/ipv4/netfilter/ip_conntrack_core.c的init_conntrack()函數(shù)中調(diào)用; (*destroy):在系統(tǒng)刪除連接時釋放該協(xié)議的特定數(shù)據(jù),不過目前都沒有使用,在net/ipv4/netfilter/ip_conntrack_core.c的destroy_conntrack()函數(shù)中調(diào)用; (*exp_matches_pkt):判斷該數(shù)據(jù)包是否是期待的新包還是以前的重發(fā)包,只是在NAT處理時使用,針對的是有序列號控制的協(xié)議,如TCP,而無序列號控制的協(xié)議無此函數(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é)議頭中哪些信息可以用來唯一識別連接,作NAT時要修改哪些信息,把這些信息添加到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中對每個要進(jìn)行NAT的IP協(xié)議定義了以下結(jié)構(gòu),每個IP協(xié)議的NAT處理就是要填寫這樣一個結(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é)議號,在IP頭中的協(xié)議號是8位,在此用unsigned int有點(diǎn)浪費(fèi) 結(jié)構(gòu)中包括以下函數(shù): (*manip_pkt):修改協(xié)議相關(guān)數(shù)據(jù),根據(jù)NAT規(guī)則來確定是修改源部分還是目的部分,在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)造一個新tuple處理將原tuple在進(jìn)行NAT后對應(yīng)的連接參數(shù),如TCP源NAT時,除了源地址必須要修改外,一般還要修改源端口,這個連接的后續(xù)包的源端口就都改這個端口值,而修改后的這個端口值必須是唯一的,和這個連接綁定,其他連接就不能再使用這個端口,如果找不到合適的tuple值,NAT將失敗,也就是說,對于多對一的NAT轉(zhuǎn)換,理論上最多只能處理65535個TCP連接,超過此數(shù)的新的TCP連接就無法進(jìn)行NAT了,對于TCP、UDP,(*unique_tuple)就是檢測查找一個新的未用端口生成一個新的tuple結(jié)構(gòu)對應(yīng)該連接,對應(yīng)ICMP,則是找一個未用的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); 對新IP協(xié)議的NAT模塊的添加和跟蹤模塊的添加類似。 5. 其他IP協(xié)議的跟蹤和NAT 下面討論其他IP協(xié)議如果要進(jìn)行跟蹤和NAT要處理哪些協(xié)議相關(guān)數(shù)據(jù): SCTP:RFC2960,協(xié)議號132,和TCP非常類似,用源端口和目的端口來識別; 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é)議號2,IGMP頭內(nèi)信息太少,沒有特殊數(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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type | Max Resp Time | Checksum | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Group Address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ GRE:RFC1701,RFC2784,協(xié)議號47,使用KEY來作為修改數(shù)據(jù),ver和protocol作為識別用的固定數(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é)議號50,只能用SPI來識別,SPI是SA的一部分,不過是不能修改的,因?yàn)镾PI是在IKE協(xié)商過程中確定的,兩邊都已經(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é)議號51,AH協(xié)議無法進(jìn)行NAT的,否則認(rèn)證就會失敗,跟蹤也只能靠SPI 6. 結(jié)論 netfilter的IP協(xié)議跟蹤和NAT處理很好地實(shí)現(xiàn)了模塊化,但除了netfilter自帶的模塊外,可處理其他IP協(xié)議也已經(jīng)不多了,只有GRE和SCTP可以新增模塊,其他協(xié)議增加模塊基本無意義。 |
|