netfilter中IP协议跟踪和NAT实现
本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。 msn: [email]yfydz_no1@hotmail.com[/email] 来源:[url]http://yfydz.[/url] 1. 前言 和匹配和目标一样,netfilter提供了模块化的IP层协议的跟踪和NAT处理,除了内核自带的模块外,用户可以根据模块格式自己编写其他IP协议的跟踪和NAT处理。注意:本文针对的跟踪和NAT模块是针对IP上层的协议,如TCP、UDP、ICMP等,而TCP、UDP上层的协议如FTP、TFTP等的跟踪和NAT使用其他方式处理,将在以后的文章中介绍。 2. tuple 在具体介绍IP协议跟踪前,需要说明一个结构ip_conntrack_tuple,这是netfilter用来描述跟踪或NAT各IP协议时需要跟踪或修改的各协议的信息,这些信息和连接的一一对应的,对于所有IP协议,协议类型、源地址、目的地址这三个参数是识别连接所必须的,具体到各个协议,就要提取出各协议的唯一特征数据,如TCP、UDP的源端口、目的端口,ICMP的ID、TYPE、CODE等值,这些值就是tuple结构要处理的数据。各协议相关数据是以联合形式定义在tuple结构中的,netfilter缺省支持TCP、UDP和ICMP协议,如果还要支持其他IP协议,如GRE、ESP、AH、SCTP等,需要在联合中添加相应的协议参数值。 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. 协议连接跟踪 netfilter中对每个要进行跟踪的IP协议定义了以下结构,每个IP协议的连接跟踪处理就是要填写这样一个结构: 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; }; 结构中包括以下参数: struct list_head list:这是将该结构挂接到协议跟踪链表中的 u_int8_t proto:协议号,在IP头中的协议号是8位,1为ICMP,2为IGMP,6为TCP,17为UDP等等 const char *name:协议名称,字符串常量 struct module *me:指向模块本身,统计模块是否被使用 结构中包括以下函数: (*pkt_to_tuple):将数据包中的信息提取到tuple结构中,如对于TCP/UDP,提取其端口值,在net/ipv4/netfilter/ip_conntrack_core.c的get_tuple()函数中调用; (*invert_tuple):将tuple中数据进行倒置,用来匹配处理连接的返回包,如对于TCP/UDP,要将端口值倒置,在net/ipv4/netfilter/ip_conntrack_core.c的invert_tuple()函数中调用; (*print_tuple):打印协议相关的tuple值,在查看/proc/net/ip_conntrack文件时调用,net/ipv4/netfilter/ip_conntrack_standalone.c; (*print_conntrack):打印协议相关的值,在查看/proc/net/ip_conntrack文件时调用,net/ipv4/netfilter/ip_conntrack_standalone.c; (*packet):判断数据包是否合法,并调整相应连接的信息,也就是实现各协议的状态检测,对于UDP等本身是无连接的协议的判断比较简单,netfilter建立一个虚拟连接,每个新发包都是合法包,只等待回应包到后连接都结束;但对于TCP之类的有状态协议必须检查数据是否符合协议的状态转换过程,这是靠一个状态转换数组实现的,在我以前的文章“什么是状态检测”中对这个数组进行了描述。在net/ipv4/netfilter/ip_conntrack_core.c的ip_conntrack_in()函数中调用; (*new):判断是否是该协议的新连接,如对于TCP,必须用SYN包表示连接开始,而对于UDP和ICMP则始终是新连接,在net/ipv4/netfilter/ip_conntrack_core.c的init_conntrack()函数中调用; (*destroy):在系统删除连接时释放该协议的特定数据,不过目前都没有使用,在net/ipv4/netfilter/ip_conntrack_core.c的destroy_conntrack()函数中调用; (*exp_matches_pkt):判断该数据包是否是期待的新包还是以前的重发包,只是在NAT处理时使用,针对的是有序列号控制的协议,如TCP,而无序列号控制的协议无此函数处理,在net/ipv4/netfilter/ip_nat_core.c的exp_for_packet()函数中调用; 最后,这些协议跟踪结构在net/ipv4/netfilter/ip_conntrack_core.c的 ip_conntrack_init()函数中挂接到协议跟踪链表中: 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协议跟踪模块,先要分析这些协议头中哪些信息可以用来唯一识别连接,作NAT时要修改哪些信息,把这些信息添加到ip_conntrack_tuple结构的联合中;然后填写该协议的ip_conntrack_protocol结构,实现结构中的内部函数;最后在ip_conntrack_init()函数中将此结构挂接到协议跟踪链表中。 4. 协议NAT netfilter中对每个要进行NAT的IP协议定义了以下结构,每个IP协议的NAT处理就是要填写这样一个结构: 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); }; 结构中包括以下参数: struct list_head list:这是将该结构挂接到协议跟踪链表中的 const char *name:协议名称,字符串常量 unsigned int protonum:协议号,在IP头中的协议号是8位,在此用unsigned int有点浪费 结构中包括以下函数: (*manip_pkt):修改协议相关数据,根据NAT规则来确定是修改源部分还是目的部分,在net/ipv4/netfilter/ip_nat_core.c的manip_pkt()函数中调用; (*in_range):判断数据包是否是要进行NAT修改,在net/ipv4/netfilter/ip_nat_core.c的in_range()、get_unique_tuple()等函数中调用; (*unique_tuple):构造一个新tuple处理将原tuple在进行NAT后对应的连接参数,如TCP源NAT时,除了源地址必须要修改外,一般还要修改源端口,这个连接的后续包的源端口就都改这个端口值,而修改后的这个端口值必须是唯一的,和这个连接绑定,其他连接就不能再使用这个端口,如果找不到合适的tuple值,NAT将失败,也就是说,对于多对一的NAT转换,理论上最多只能处理65535个TCP连接,超过此数的新的TCP连接就无法进行NAT了,对于TCP、UDP,(*unique_tuple)就是检测查找一个新的未用端口生成一个新的tuple结构对应该连接,对应ICMP,则是找一个未用的ID值,该函数在net/ipv4/netfilter/ip_nat_core.c的get_unique_tuple()函数中调用; (*print):打印struct ip_conntrack_tuple中的协议相关信息; (*print_range):打印struct ip_nat_range结构中要进行NAT修改的那部分协议信息; 最后,这些协议跟踪结构在net/ipv4/netfilter/ip_nat_core.c的ip_nat_init()函数中挂接到协议NAT链表中: list_append(&protos, &ip_nat_protocol_tcp); list_append(&protos, &ip_nat_protocol_udp); list_append(&protos, &ip_nat_protocol_icmp); 对新IP协议的NAT模块的添加和跟踪模块的添加类似。 5. 其他IP协议的跟踪和NAT 下面讨论其他IP协议如果要进行跟踪和NAT要处理哪些协议相关数据: SCTP:RFC2960,协议号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,协议号2,IGMP头内信息太少,没有特殊数据供识别 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,协议号47,使用KEY来作为修改数据,ver和protocol作为识别用的固定数据 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,协议号50,只能用SPI来识别,SPI是SA的一部分,不过是不能修改的,因为SPI是在IKE协商过程中确定的,两边都已经预先知道,一旦修改了就匹配不到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,协议号51,AH协议无法进行NAT的,否则认证就会失败,跟踪也只能靠SPI 6. 结论 netfilter的IP协议跟踪和NAT处理很好地实现了模块化,但除了netfilter自带的模块外,可处理其他IP协议也已经不多了,只有GRE和SCTP可以新增模块,其他协议增加模块基本无意义。 |
|
来自: womking > 《Linux Network 》