Keep looking Donot settle
分类: 嵌入式
2014-12-22 09:59:47
连接跟踪模块是NAT的基础,但作为一个单独的模块实现。它用于对包过滤功能的一个扩展,管理单个连接(特别是TCP连接),并负责为现有的连接分配输入、输出和转发IP数据报,从而基于连接跟踪来实现一个“基于状态”的防火墙。
当连接跟踪模块注册一个连接建立包之后,将生成一个新的连接记录。此后,所有属于此连接的数据报都被唯一地分配给这个连接。如果一段时间内没有流量而超时,连接将被删除,然后其他需要使用连接跟踪的模块就可以重新使用这个连接所释放的资源了。
如果需要用于比传输协议更上层的应用协议,连接跟踪模块还必须能够将建立的数据连接与现有的控制连接相关联。
Conntrack在Netfilter中是基于下列HOOK的:
NF_IP_PRE_ROUTING
NF_IP_LOCAL_OUT
同时当使用NAT时,Conntrack也会有基于NF_IP_LOCAL_IN和NF_IP_POST_ROUTING的,只不过优先级很小。
在所有的HOOK上,NF_IP_PRI_CONNTRACK的优先级是最高的(-200),这意味着每个数据报在进入和发出之前都首先要经过Conntrack模块,然后才会被传到钩在HOOK上的其它模块。
Conntrack模块的接口位于文件net/ipv4/netfilter/ip_conntrack_standalone.c中。
多元组
在连接跟踪模块中,使用所谓的“tuple”,也就是多元组,来小巧锐利地描述连接记录的关键部分,主要是方便连接记录的管理。其对应的数据结构是ip_conntrack_tuple(位于include/linux/netfilter_ipv4/ip_conntrack_tuple.h,Line38):
struct
ip_conntrack_tuple
{
struct ip_conntrack_manip
src;
struct {
u_int32_t ip;
union {
u_int16_t all;
struct { u_int16_t port; }
tcp;
struct { u_int16_t port; }
udp;
struct { u_int8_t type, code; }
icmp;
} u;
u_int16_t protonum;
} dst;
};
从它的定义可以看出,一个多元组实际上包括两个部分:一是所谓的“unfixed”部分,也就是源地址以及端口;二是所谓的“fixed”部分,也就是目的地址、端口以及所用的协议。这样,连接的两端分别用地址+端口,再加上所使用的协议,一个tuple就可以唯一地标识一个连接了(对于没有端口的icmp协议,则用其它东东标识)。
连接记录
那么真正的完整连接记录则是由数据结构ip_conntrack(位于include/linux/netfilter_ipv4/ip_conntrack.h,Line160)来描述的,其成员有:
`struct nf_conntrack
ct_general;`:nf_conntrack结构定义于include/linux/skbuff.h,Line89,其中包括一个计数器use和一个destroy函数。计数器use对本连接记录的公开引用次数进行计数
`struct ip_conntrack_tuple_hash
tuplehash[IP_CT_DIR_MAX];`:其中的IP_CT_DIR_MAX是一个枚举类型ip_conntrack_dir(位于include/linux/netfilter_ipv4/ip_conntrack_tuple.h,Line65)的第3个成员,从这个结构实例在源码中的使用看来,实际上这是定义了两个tuple多元组的hash表项tuplehash[IP_CT_DIR_ORIGINAL/0]和tuplehash[IP_CT_DIR_REPLY/1],利用两个不同方向的tuple定位一个连接,同时也可以方便地对ORIGINAL以及REPLY两个方向进行追溯
`unsigned long
status;`:这是一个位图,是一个状态域。在实际的使用中,它通常与一个枚举类型ip_conntrack_status(位于include/linux/netfilter_ipv4/ip_conntrack.h,Line33)进行位运算来判断连接的状态。其中主要的状态包括:
IPS_EXPECTED(_BIT),表示一个预期的连接
IPS_SEEN_REPLY(_BIT),表示一个双向的连接
IPS_ASSURED(_BIT),表示这个连接即使发生超时也不能提早被删除
IPS_CONFIRMED(_BIT),表示这个连接已经被确认(初始包已经发出)
`struct timer_list
timeout;`:其类型timer_list位于include/linux/timer.h,Line11,其核心是一个处理函数。这个成员表示当发生连接超时时,将调用此处理函数
`struct list_head
sibling_list;`:所谓“预期的连接”的链表,其中存放的是我们所期望的其它相关连接
`unsigned int
expecting;`:目前的预期连接数量
`struct ip_conntrack_expect
*master;`:结构ip_conntrack_expect位于include/linux/netfilter_ipv4/ip_conntrack.h,Line119,这个结构用于将一个预期的连接分配给现有的连接,也就是说本连接是这个master的一个预期连接
`struct ip_conntrack_helper
*helper;`:helper模块。这个结构定义于include/linux/netfilter_ipv4/ip_conntrack_helper.h,Line11,这个模块提供了一个可以用于扩展Conntrack功能的接口。经过连接跟踪HOOK的每个数据报都将被发给每个已经注册的helper模块(注册以及卸载函数分别为ip_conntrack_helper_register()以及ip_conntrack_helper_unregister(),分别位于net/ipv4/netfilter/ip_conntrack_core.c,Line1136、1159)。这样我们就可以进行一些动态的连接管理了
`struct nf_ct_info
infos[IP_CT_NUMBER];`:一系列的nf_ct_info类型(定义于include/linux/skbuff.h ,Line92,实际上就是nf_conntrack结构)的结构,每个结构对应于某种状态的连接。这一系列的结构会被sk_buff结构的nfct指针所引用,描述了所有与此连接有关系的数据报。其状态由枚举类型ip_conntrack_info定义(位于include/linux/netfilter_ipv4/ip_conntrack.h,Line12),共有5个成员:
IP_CT_ESTABLISHED:数据报属于已经完全建立的连接
IP_CT_RELATED: 数据报属于一个新的连接,但此连接与一个现有连接相关(预期连接);或者是ICMP错误
IP_CT_NEW:数据报属于一个新的连接
IP_CT_IS_REPLY:数据报属于一个连接的回复
IP_CT_NUMBER:不同IP_CT类型的数量,这里为7,NEW仅存于一个方向上
为NAT模块设置的信息(在条件编译中)
hash表
Netfilter使用一个hash表来对连接记录进行管理,这个hash表的初始指针为*ip_conntrack_hash,位于net/ipv4/netfilter/ip_conntrack_core.c,Line65,这样我们就可以使用ip_conntrack_hash[num]的方式来直接定位某个连接记录了。
而hash表的每个表项则由数据结构ip_conntrack_tuple_hash(位于include/linux/netfilter_ipv4/ip_conntrack_tuple.h,Line86)描述 :
struct
ip_conntrack_tuple_hash
{
struct list_head
list;
struct ip_conntrack_tuple
tuple;
struct ip_conntrack
*ctrack;
};
可见,一个hash表项中实质性的内容就是一个多元组ip_conntrack_tuple;同时还有一个指向连接的ip_conntrack结构的指针;以及一个链表头(这个链表头不知是干嘛的)。
有了以上的数据结构,连接跟踪的具体实现其实就非常简单而常规了,无非就是初始化、连接记录的创建、在连接跟踪hash表中搜索并定位数据报、将数据报转换为一个多元组、判断连接的状态以及方向、超时处理、协议的添加、查找和注销、对不同协议的不同处理、以及在两个连接跟踪相关的HOOK上对数据报的处理等。
下面重点说明一下我在分析中遇到的几个比较重要或者比较难理解的地方:
可以将预期连接看作父子关系来理解,如图
http://blog.chinaunix.net/photo/24896_061206192612.jpg
ip_conntrack的状态转换分两种,同样用图来描述。首先是正常的状态转换,如图http://blog.chinaunix.net/photo/24896_061206192631.jpg,
然后是ICMP error时的状态转换(由函数icmp_error_track()来判断,位于net/ipv4/netfilter/ip_conntrack_core.c,Line495),
如图http://blog.chinaunix.net/photo/24896_061206192648.jpg
在经过HOOK中的NF_IP_PRE_ROUTING时(函数ip_conntrack_in(),位于net/ipv4/netfilter/ip_conntrack_core.c,Line796),由于外来的数据报有可能是经过分片的,所以必须对分片的情形进行处理,将IP数据报组装后才能分配给连接。
具体的操作是首先由函数ip_ct_gather_frags()对分片的数据报进行收集,然后调用ip_defrag()函数(位于net/ipv4/ip_fragment.c,Line632)组装之
由于我们可能要添加新的协议,所以单独对协议的扩展进行分析。
各种协议使用一个全局的协议列表存放,即protocol_list(位于include/linux/netfilter_ipv4/ip_conntrack_core.h,Line21),使用结构ip_conntrack_protocol(位于include/linux/netfilter_ipv4/ip_conntrack_protocol.h,Line6)来表示:
struct ip_conntrack_protocol
{
struct list_head list;
u_int8_t proto; //协议号
const char *name;
int
(*pkt_to_tuple)(const void *datah, size_t datalen,
struct ip_conntrack_tuple
*tuple);
int
(*invert_tuple)(struct ip_conntrack_tuple *inverse,
const struct ip_conntrack_tuple
*orig);
unsigned int (*print_tuple)(char
*buffer,
const struct ip_conntrack_tuple
*);
unsigned int (*print_conntrack)(char
*buffer,
const struct ip_conntrack
*);
int
(*packet)(struct ip_conntrack *conntrack,
struct iphdr *iph, size_t
len,
enum ip_conntrack_info
ctinfo);
int
(*new)(struct ip_conntrack *conntrack, struct iphdr *iph,
size_t len);
void (*destroy)(struct ip_conntrack
*conntrack);
int
(*exp_matches_pkt)(struct ip_conntrack_expect *exp,
struct sk_buff **pskb);
struct module *me;
};
其中重要成员:
`int
(*pkt_to_tuple)(……)`:其指向函数的作用是将协议加入到ip_conntrack_tuple的dst子结构中
`int
(*invert_tuple)(……)`:其指向函数的作用是将源和目的多元组中协议部分的值进行互换,包括IP地址、端口等
`unsigned int
(*print_tuple)(……)`:其指向函数的作用是打印多元组中的协议信息
`unsigned int
(*print_conntrack)(……)`:其指向函数的作用是打印整个连接记录
`int
(*packet)(……)`:其指向函数的作用是返回数据报的verdict值
`int
(*new)(……)`:当此协议的一个新连接发生时,调用其指向的这个函数,调用返回true时再继续调用packet()函数
`int
(*exp_matches_pkt)(……)`:其指向函数的作用是判断是否有数据报匹配预期的连接
添加/删除协议使用的是函数ip_conntrack_protocol_register()以及ip_conntrack_protocol_unregister(),分别位于net/ipv4/netfilter/ip_conntrack_standalone.c,Line298 & 320,其工作就是将ip_conntrack_protocol添加到全局协议列表protocol_list。
from: http://blog.sina.com.cn/s/blog_a31ff26901013n0i.html