netfilter中IP协议跟踪和NAT实现
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可以新增模块,其他协议增加模块基本无意义。