并没有任何特定的先决条件。从2.6.23到2.6.25,API保持了极其的相识。在2.6.26(-rc1)中有一点点类型变化。Xtables-addons并没有提供包括连接跟踪在内的可移植的API,因为并没有模块的合入需要这个,但是编译系统还是非常方便的使用。
6 nf_conn结构体
有些时候有必要去获取连接的参数。函数nf_ct_get可以找到数据包对应的连接,如果存在,那么将会返回这个连接并且返回连接状态。为了使用nf_ct_get函数,你需要去包含<net/netfilter/nf_conntrack.h>,同时为了使用enum ip_conntrack_info,你也需要包含<linux/netfilter/nf_conntrack_common.h>。这里将这个头文件分布在linux/目录下,是为了将其导出到用户空间,这些静态值,例如IP_CT_NEW在用户空间也是非常有用的,当然nf_ct_get只能在内核中可以使用。
#include <linux/netfilter/nf_conntrack_common.h>
enum ip_conntrack_infoctinfo;
struct nf_conn *ct;
ct = nf_ct_get(skb,&ctinfo);
存在着众多的连接状态,在Xtables的conntrack的匹配中,你可以匹配他们,或者通过xt_LOGMARK打印出连接的信息,这是一个target的扩展。注意,连接跟踪子系统将在raw表后面,mangle表之前处理。
if (ct == NULL)
pr_info("Thisis --ctstate INVALID\n");
当连接跟踪子系统将这个连接声明为INVALID,那么ct就有可能是NULL,例如在一个已经存在的连接上面发送了一个TCP SYN。
else if (ct ==&nf_conntrack_untracked)
pr_info("Thisone is not tracked\n");
在raw表中,NOTRACK的目标将会使数据包从连接跟踪中豁免;这个特性对TARPIT目标非常有用。它也通常被用于任何你想要丢弃的数据包,但是一般来说,人们并不会抱怨因为它通常发生在重复的规则中。如果在filter表中仅仅是丢弃数据包,那将意味着对应的连接跟踪表象将会一直保存到其超时,在一般情况下,这种处理方式可以很好的工作。默认的超时时间依赖于具体的协议和实现,你丢弃的连接如果是NEW,那么一般为30秒到2分钟。
else if (ctinfo % IP_CT_IS_REPLY == IP_CT_NEW)
pr_info("Thisis the first packet in a connection\n");
else if (ctinfo % IP_CT_IS_REPLY == IP_CT_RELATED)
pr_info("WelcomeMr. Bond, we have been expecting you\n");
else if (ctinfo % IP_CT_IS_REPLY == IP_CT_ESTABLISHED)
pr_info("Youcan figure out this one!\n");
如果ct不是NULL,并且不是无效的连接跟踪表项,例如没有跟踪的包,那么这个连接就是有效的,并且它的状态可以在ctinfo中找到。enumip_conntrack_ctinfo混合了连接的状态和数据包的方向。
IP_CT_NEW,新连接
IP_CT_RELATED,同样也是新连接,不过是一个期待连接。
IP_CT_ESTABLISHED,连接已经建立,当前数据包在原始方向。
IP_CT_ESTABLISHED +IP_CT_IS_REPLY,连接已经建立,数据包在回复方向。
IP_CT_RELATED + IP_CT_IS_REPLY,期待连接开始,同时数据包在回复方向。非常惊奇的是第一个包可以在回复方向(注意:回复方向的连接不是原始的那个)。这个实际是用于ICMP回复。
IP_CT_NEW + IP_CT_IS_REPLY没有使用,并且是不合法的。
通过使用ctinfo %IP_CT_IS_REPLY(在这种情况下类似于ctinfo & ~IP_CT_IS_REPLY),提取连接跟踪的状态。数据包的方向也可以通过ctinfo /IP_CT_IS_REPLY来获取,但是一个更方便的宏CTINFO2DIR(ctinfo)可以使用,定义在<linux/netfilter/nf_conntrack_tuple_common.h>。
连接跟踪器
连接跟踪器是连接跟踪架构中非常重要的一个部分,并且关系到了状态防火墙的实现。它的主要工作是将一个IP数据包找到对应的连接信息,并保证数据包和其参数的正确性。例如TCP数据流,检测窗口大小和TCP状态转换是否正确。
最简单的情况,仅仅是将源地址和目标地址拷贝到structnf_conntrack_tuple,structnf_conntrack_tuple将会和其他一起连接形成一个已知连接表。连接跟踪分割成两个类别,layer-3和layer-4模块,以实现最大的模块化。当然也存在layer-5的连接跟踪器,称为连接辅助,因为这些函数并不是为当前连接工作的,而是为了以后的新连接。
7 layer-3连接跟踪器
7.1 目标
当然,问题来了,这章将会充满着什么古怪的想法,这些答案将会如何被分解?
荟萃一个在内核中并不存在的layer-3连接跟踪器将会使一个非常困难的工作。不仅仅因为IPv4和IPv6是最具统治的协议,更因为内核现在并没有在其他协议中有netfilter的钩子,当然除了特殊的以外。
这就导致了一些非常有趣的任务。Jan有一个为IPX写的连接跟踪器,但是振兴老的DOS游戏最后被证明是一个长期的认为,因为现代操作系统的技术问题。当我们发现在ARP的输入输出路径上并没有netfilter的钩子,诉诸arp的连接跟踪也并不是那么有收获,所以这个想法也就报废了,因为我们不想读者去修改内核,编译并安装等等流程。
7.2 结构体定义
Layer-3跟踪器结构体定义在<net/netfilter/nf_conntrack_l3proto.h>。它包含了数据包到tuple的关联,tuple翻转函数和去获取4层协议号的函数。
struct nf_conntrack_l3proto {
const char *name;
uint16_t l3proto;
bool (*pkt_to_tuple)(const struct sk_buff *skb, unsigned int nhoff,
struct nf_conntrack_tuple *tuple);
bool (*invert_tuple)(struct nf_conntrack_tuple *inverse,
const struct nf_conntrack_tuple *orig);
int (*print_tuple)(struct seq_file *,
const struct nf_conntrack_tuple *);
int (*get_l4proto)(const struct sk_buff *skb, unsigned int nhoff,
unsigned int *dataoff, uint8_t *protonum);
struct module *me;
};
当缺少实际的四层部分时,三层连接跟踪器是基本没什么实际用途。函数pkt_to_tuple和invert_tuple将会被调用,但是仍然没有创建任何的连接跟踪表项,你通过conntrack –L命令或者是/proc/net/nf_conntrack里面也就看不到相应的表项。只有在四层连接跟踪器注册才能有连接跟踪和事件产生。
函数get_l4proto应该检查数据包和返回4层协议号,它也有可能返回-NF_ACCEPT,那么意味着这个连接没有被正常跟踪。
7.3 通用的L4跟踪
虽然四层跟踪仅仅只针对最通用的协议,但是很多的协议仍然可以被跟踪,因为可以使用通用的跟踪器。AH和ESP就是这个类别的两个例子。
通用跟踪器将所有的数据包的四层协议映射到一个连接中,这也是逻辑上可以做到的。这个连接跟踪表将会看起来像:
# conntrack -L |grep "^unknown"
unknown 50 537 src=192.168.0.137dst=192.168.16.34 packets=12 bytes=1456
src=192.168.16.34 dst=192.168.0.137packets=12 bytes=2704 mark=0 use=1
我们希望读者可以原谅这个章节太短。同时我们确认写三层的跟踪器和四层的非常相似,我们将在下一章节将四层跟踪。
8 四层连接跟踪器
8.1 结构体定义
四层跟踪器的定义包含在<net/netfilter/nf_conntrack_l4proto.h>,这些回调函数的顺序从error到destroy排序,描述了数据包流程的整个顺序。
struct nf_conntrack_l4proto {
const char *name;
uint16_t l3proto;
uint8_t l4proto;
int (*error)(struct sk_buff *, unsigned int dataoff,
enum ip_conntrack_info *ctinfo,unsigned int pf,
unsigned int hooknum);
bool (*pkt_to_tuple)(const struct sk_buff *skb,
unsigned int dataoff,
struct nf_conntrack_tuple *tuple);
bool (*invert_tuple)(struct nf_conntrack_tuple *inverse,
const struct nf_conntrack_tuple *original);
int (*packet)(struct nf_conn *ct, const struct sk_buff *skb,
unsigned int dataoff, enum ip_conntrack_info ctinfo,
unsigned int pf, unsigned int hooknum);
bool (*new)(struct nf_conn *ct, const struct sk_buff *skb,
unsigned int dataoff);
void (*destroy)(struct nf_conn *ct);
int (*print_conntrack)(struct seq_file *s,
const struct nf_conn *ct);
int (*print_tuple)(struct seq_file *s,
const struct nf_conntrack_tuple *tuple);
struct module *me;
};
它也有数据包到tuple和关联的tuple翻转,同时还包含了“packet”、“new”和“destroy”。
8.2 目标
在这一章节我们将会关注一下ESP连接跟踪模块。它当然可以工作,并且没有什么实际的域是需要处理,因为通用的连接跟踪已经处理了通用的功能。如果你需要跟踪一些特殊的SPI数据流,那么这个模块适合你。
ESP是一个加密的,所以除非是两端,否则没有任何途径可以查看它里面的数据。即使如此,对于管道模型和其他任何类型的(非加密)管道和封装,我们并不经常希望检测里面的内容,而是跟踪管道连接本身。
8.3 模块初始化
结构体成员name是用于提供给用户空间的短字符串,尽量保持简单,并且不能有空格。成员l3proto和l4proto分别标示了3层和4层对应的辅助函数。
static struct nf_conntrack_l4protoesp_ctrack_reg __read_mostly = {
.name ="esp",
.l3proto =NFPROTO_IPV6,
.l4proto =IPPROTO_ESP,
剩下的结构体组成成员是函数指针:
esp_ctrack_pkt2tuple 将数据包中的信息映射到tuple中。
esp_ctrack_new 当创建一个新的连接跟踪表项时就会调用。
esp_ctrack_packet 数据包处理函数,例如更新内部跟踪状态。TCP使用这个函数进行状态转换。
esp_ctrack_invtuple 翻转tuple
esp_ctrack_prct 打印连接跟踪表项
esp_ctrack_prtuple 打印连接tuple
.pkt_to_tuple = esp_ctrack_pkt2tuple
.new =esp_ctrack_new
.packet =esp_ctrack_packet,
.invert_tuple =esp_ctrack_invtuple,
.print_conntrack =esp_ctrack_prct,
.print_tuple =esp_ctrack_prtuple,
.me = THIS_MODULE,
};
8.4 tuple结构体(五元组)
union nf_conntrack_man_proto包含了“协议可变”部分,(在NAT情况下,这部分保持的是源地址,在外网IP和内网IP间切换)。
注释:我认为更不会让人迷糊的介绍是查看structnf_conntrack_tuple,这个结构体包含有src和dst,其中dst是固定的,因为例如你访问baidu.com,dst就是百度,但是src因为存在NAT,那么就有可能是内网IP和公网IP。
8.5 数据包到连接跟踪
当一个数据包进入连接跟踪子系统,它将被传递到对应的4层连接跟踪模块,进入pkt_to_tuple钩子。这个函数是将数据包映射到连接五元组中。后者是在netfilter中唯一确定一个连接。
8.6 五元组反转
当一个数据包达到,它会和已经存在的tuple进行对比,看是否能找到另一个方向的对应连接。
它们并不关注是从哪个网口进来的,因为策略路由情况下,数据包可能会走不同的路径。
static bool tcp_invert_tuple(struct nf_conntrack_tuple *tuple,
const struct nf_conntrack_tuple *orig)
{
inverse->src.u.all= original->dst.u.all;
inverse->dst.u.all= original->src.u.all;
}
注释:例如有一个出去的数据包,那么通过反转,在reply方向就赋予了dst和src,当然这个src可能还是内网地址。这样等到回包时,就可以很快的找到对应的连接。
8.7 输出表项
8.8 总结
9 连接跟踪辅助函数
注释:理解这个应该先应该知道ALG(应用层网关),例如FTP等,在应用层数据中涉及到内网IP,同时存在NAT,那么就需要将内网IP换成外网IP,这些就是辅助函数做的事情。
struct nf_conntrack_helper{
const char *name;
unsigned int max_expected;
unsigned int timeout;
/* Tuple of connection to analyze */
struct nf_conntrack_tuple tuple;
/* Our helpful function */
int (*help)(struct sk_buff *skb, unsigned int protoff,
struct nf_conn *ct, enum ip_conntrack_info ctinfo);
void (*destroy)(struct nf_conn *ct);
struct module *me;
};