Chinaunix首页 | 论坛 | 博客
  • 博客访问: 529872
  • 博文数量: 120
  • 博客积分: 3030
  • 博客等级: 中校
  • 技术积分: 1445
  • 用 户 组: 普通用户
  • 注册时间: 2006-03-05 01:00
文章存档

2011年(1)

2009年(2)

2008年(32)

2007年(33)

2006年(52)

我的朋友

分类: LINUX

2008-06-03 15:31:17

[保留] Netfliter状态跟踪之动态协议的实现浅析(tftp实现)

作者:独孤九贱  发表于:2006-10-19 13:50:57

之所以叫“浅析”,主要是分析其流程,很多细节的地方没有一一注解出来,之所以以tftp为范本来剖析,主要是因为它简单,呵呵,这篇贴子,作为旧贴
%3D1%26filter%3Ddigest
的一个补充,好为对Netfliter的状态跟踪分析的结束……也希望,下一步“Netfliter的地址转换的实现”能早点写出来……

注:这些贴子,包括iptables,Netfilter的包过滤,Netfliter的状态检测,都只是笔记性质的贴子,供有共同兴趣的朋友一起讨论,其中有不少错误的地方,希望大家指正,(并不是谦虚,我自己也在不断地改正和完善:em02:)!另,照旧,源码版本是2.6.12

1、模块的注册

源码在ip_conntrack_tftp.c中:
init函数中定义了
static struct ip_conntrack_helper tftp[MAX_PORTS];
并初始化它,并注册它:
memset(&tftp, 0, sizeof(struct ip_conntrack_helper));

……
ret=ip_conntrack_helper_register(&tftp);


tftp是一个数组,最大允许MAX_PORTS个,并且变量ports_c决定其个数,因为它做为注册时for循环的终值,目前,只注册了一个tftp。

tftp是一个ip_conntrack_helper类型,我在后文中,会把它叫做“helper”模块,也就是说,初始化函数中,调用ip_conntrack_helper_register函数注册了一个tftp的helper模块。

在tftp的成员的赋初始化值的时候,我们可以对照理解struct ip_conntrack_helper结构的许多重要的成员:
	tftp.tuple.dst.protonum = IPPROTO_UDP;		//协议

tftp.tuple.src.u.udp.port = htons(ports); //目标端口,即69,这样,UDP:69成为认识tftp的唯一标志
tftp.mask.dst.protonum = 0xFF; //目标地址掩码,以及下面一个源端口掩码,以做比较之用
tftp.mask.src.u.udp.port = 0xFFFF;
tftp.max_expected = 1; //最大expect,这是什么东东?后面会详解
tftp.timeout = 5 * 60; /* 5 minutes */ //超时时间
tftp.me = THIS_MODULE;
tftp.help = tftp_help; //这个函数指针是最重要的东东了,后面再来分析它的具体作用


ip_conntrack_helper_register函数实质上是把该模块添加进以全局变量helpers为首的链表中去:

int ip_conntrack_helper_register(struct ip_conntrack_helper *me)

{
BUG_ON(me->timeout == 0);
WRITE_LOCK(&ip_conntrack_lock);
list_prepend(&helpers, me);
WRITE_UNLOCK(&ip_conntrack_lock);

return 0;
}


OK,tftp的helper模块被注册了,它什么时候被调用?以及它有什么用呢??

回忆在连接跟踪的初时化时,注册的两个钩子:
/*连接跟踪初始化时,注册helper Hook*/
static struct nf_hook_ops ip_conntrack_helper_out_ops = {
.hook = ip_conntrack_help,
.owner = THIS_MODULE,
.pf = PF_INET,
.hooknum = NF_IP_POST_ROUTING,
.priority = NF_IP_PRI_CONNTRACK_HELPER, /*此优先级比同Hook上的ip_confirm的高*/
};

static struct nf_hook_ops ip_conntrack_helper_in_ops = {
.hook = ip_conntrack_help,
.owner = THIS_MODULE,
.pf = PF_INET,
.hooknum = NF_IP_LOCAL_IN,
.priority = NF_IP_PRI_CONNTRACK_HELPER,
};

对于中转包过滤来讲,我们关心第一个钩子,它注册在NF_IP_POST_ROUTING Hook上,并且,比我们讲过的ip_confirm优先级要高。
这样,也就是数据包经过这个Hook点时,ip_conntrack_help 函数将被调用。

2.我的例子
结合一个实际的tftp传输来分析代码,先来看这个例子(该例取自《TCP/IP详解卷一》p161)

1.	192.168.0.1:1106	->	192.168.1.1:69		udp	19	PRQ	"test1.c"

2. 192.168.1.1:1077 -> 192.168.0.1:1106 udp 516
3. 192.168.0.1:1106 -> 192.168.1.1:1077 udp 4
4. 192.168.1.1:1077 -> 192.168.0.1:1106 udp 454
5. 192.168.0.1:1106 -> 192.168.1.1:1077 udp 4


第1行,是192.168.0.1发出了一个“读请求”,文件名是test1.c;
第2行是,192.168.1.1 回应了读请求,将文件的数据,共516字节发送给请求者,注意,这里的来源端口不是69,而变成了1077;
第3行是一个回应包
第4,5行类似;

对于第1行,即新请求一个连接,回忆我前一次的描述,连接跟踪模块会执行以下函数:
在NF_IP_PRE_ROUTING Hook处调用钩子函数ip_conntrack_in,接着进入resolve_normal_ct函数,由于这是个新连接,所以,找不
到与之对应的tuple,于是进入了init_conntrack,初始化一个连接。

static struct ip_conntrack_tuple_hash *

init_conntrack(const struct ip_conntrack_tuple *tuple,
       struct ip_conntrack_protocol *protocol,
       struct sk_buff *skb)
{
struct ip_conntrack_expect *exp;

……
exp = find_expectation(tuple);

if (exp) {
DEBUGP("conntrack: expectation arrives ct=%p exp=%p\n",
conntrack, exp);
/* Welcome, Mr. Bond.  We've been expecting you... */
__set_bit(IPS_EXPECTED_BIT, &conntrack->status);
conntrack->master = exp->master;
#if CONFIG_IP_NF_CONNTRACK_MARK
conntrack->mark = exp->master->mark;
#endif
nf_conntrack_get(&conntrack->master->ct_general);
CONNTRACK_STAT_INC(expect_new);
} else {
conntrack->helper = ip_ct_find_helper(&repl_tuple);

CONNTRACK_STAT_INC(new);
}
……
}


exp是一个struct ip_conntrack_expect类型,find_expectation看样子应该是根据该数据包对应的tuple,查找一个struct ip_conntrack_expect类型的节点,expect是什么东东?暂时不管它,因为我们目前还没有提到它,所以,find_expectation什么也查不到,那么接下来那个if...else...则会进入else判断:

else 

{
     conntrack->helper = ip_ct_find_helper(&repl_tuple);
     CONNTRACK_STAT_INC(new);
}


ip_ct_find_helper函数根据当前数据包对应的repl_tuple,在helpers链表中查找是否有相应的helper模块:
PS:当前数据包的tuple是:
192.168.0.1:1106 192.168.1.1:69 udp
则repl_tuple为:
192.168.1.1:69 192.168.0.1:1106 udp

static struct ip_conntrack_helper *ip_ct_find_helper(const struct ip_conntrack_tuple *tuple)

{
return LIST_FIND(&helpers, helper_cmp,
 struct ip_conntrack_helper *,
 tuple);
}


比较函数是helper_cmp:
static inline int helper_cmp(const struct ip_conntrack_helper *i,

     const struct ip_conntrack_tuple *rtuple)
{
return ip_ct_tuple_mask_cmp(rtuple, &i->tuple, &i->mask);
}


实际转向给了ip_ct_tuple_mask_cmp函数:
static inline int ip_ct_tuple_mask_cmp(const struct ip_conntrack_tuple *t,

       const struct ip_conntrack_tuple *tuple,
       const struct ip_conntrack_tuple *mask)
{
return !(((t->src.ip ^ tuple->src.ip) & mask->src.ip)
 || ((t->dst.ip ^ tuple->dst.ip) & mask->dst.ip)
 || ((t->src.u.all ^ tuple->src.u.all) & mask->src.u.all)
 || ((t->dst.u.all ^ tuple->dst.u.all) & mask->dst.u.all)
 || ((t->dst.protonum ^ tuple->dst.protonum)
     & mask->dst.protonum));
}


对照一下tftp模块初始化时的helper的各成员值和当前数据包repl_tuple(192.168.1.1:69 192.168.0.1:1106 udp),可以发现,最终tftp注册的helper模块将被正确地查找出来!!
这样,当前tftp的连接conntrack的helper指针就指向了tftp模块。这一点非常重要。
conntrack->helper = ip_ct_find_helper(&repl_tuple);


这个数据包继续前进,当它进入NF_IP_POST_ROUTING Hook点时,会进入ip_conntrack_help函数:

/*根据数据包,查找对应的连接,如果此连接有关链的helper模块,则调用help函数*/
static unsigned int ip_conntrack_help(unsigned int hooknum,

      struct sk_buff **pskb,
      const struct net_device *in,
      const struct net_device *out,
      int (*okfn)(struct sk_buff *))
{
struct ip_conntrack *ct;
enum ip_conntrack_info ctinfo;

/* This is where we call the helper: as the packet goes out. */
ct = ip_conntrack_get(*pskb, &ctinfo);
if (ct && ct->helper) {
unsigned int ret;
ret = ct->helper->help(pskb, ct, ctinfo);
if (ret != NF_ACCEPT)
return ret;
}
return NF_ACCEPT;
}


这个函数只有一件事,就是发现了tftp的这个连接(192.168.0.1:1106 192.168.1.1:69 udp),有相应的helper模块,于是,调用helper模块的help函数,于是,我们再回来看ip_conntrack_tftp.c中,这个help函数的实现:
static int tftp_help(struct sk_buff **pskb,

     struct ip_conntrack *ct,
     enum ip_conntrack_info ctinfo)
{
struct tftphdr _tftph, *tfh;
struct ip_conntrack_expect *exp;
unsigned int ret = NF_ACCEPT;

tfh = skb_header_pointer(*pskb,
 (*pskb)->nh.iph->ihl*4+sizeof(struct udphdr),
 sizeof(_tftph), &_tftph);
if (tfh == NULL)
return NF_ACCEPT;

switch (ntohs(tfh->opcode)) {
/* RRQ and WRQ works the same way */
case TFTP_OPCODE_READ:
case TFTP_OPCODE_WRITE:
DEBUGP("");
DUMP_TUPLE(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
DUMP_TUPLE(&ct->tuplehash[IP_CT_DIR_REPLY].tuple);

exp = ip_conntrack_expect_alloc();
if (exp == NULL)
return NF_DROP;

exp->tuple = ct->tuplehash[IP_CT_DIR_REPLY].tuple;
exp->mask.src.ip = 0xffffffff;
exp->mask.dst.ip = 0xffffffff;
exp->mask.dst.u.udp.port = 0xffff;
exp->mask.dst.protonum = 0xff;
exp->expectfn = NULL;
exp->master = ct;

DEBUGP("expect: ");
DUMP_TUPLE(&exp->tuple);
DUMP_TUPLE(&exp->mask);
if (ip_nat_tftp_hook)
ret = ip_nat_tftp_hook(pskb, ctinfo, exp);
else if (ip_conntrack_expect_related(exp) != 0) {
ip_conntrack_expect_free(exp);
ret = NF_DROP;
}
break;
case TFTP_OPCODE_DATA:
case TFTP_OPCODE_ACK:
DEBUGP("Data/ACK opcode\n");
break;
case TFTP_OPCODE_ERROR:
DEBUGP("Error opcode\n");
break;
default:
DEBUGP("Unknown opcode\n");
}
return NF_ACCEPT;
}


这个函数很简单,它只关注tftp操作码的读和写,发现,如果是这两个操作码的话,就先分配一个struct ip_conntrack_expect结构:
	exp = ip_conntrack_expect_alloc();


然后,初始化它:
	exp->tuple = ct->tuplehash[IP_CT_DIR_REPLY].tuple;

exp->mask.src.ip = 0xffffffff;
exp->mask.dst.ip = 0xffffffff;
exp->mask.dst.u.udp.port = 0xffff;
exp->mask.dst.protonum = 0xff;
exp->expectfn = NULL;
exp->master = ct;


最后,将它注册:
	ip_conntrack_expect_related(exp) != 0


是到了解释expect的时候了:
对于tftp来讲,它的请求连接是:
192.168.0.1:1106 -> 192.168.1.1:69 udp
我们希望它同其它普通协议一样,应答包是:
192.168.1.1:69 -> 192.168.0.1:1106 udp
而不是:
192.168.1.1:1077 -> 192.168.0.1:1106 udp

所以,这个expect就用来存储,该连接所“期望”的应答包,仅此而已,这也是给它的成员tuple初始化时,初始化的是当前连接的应答的tuple的原因:
exp->tuple = ct->tuplehash[IP_CT_DIR_REPLY].tuple;

后面的那些mask,用于比较用。master指针让expect指向了当前连接。

至于注册,它与注册helper一样,是一个插入链表的过程:

int ip_conntrack_expect_related(struct ip_conntrack_expect *expect)

{
struct ip_conntrack_expect *i;
int ret;

DEBUGP("ip_conntrack_expect_related %p\n", related_to);
DEBUGP("tuple: "); DUMP_TUPLE(&expect->tuple);
DEBUGP("mask:  "); DUMP_TUPLE(&expect->mask);

WRITE_LOCK(&ip_conntrack_lock);
list_for_each_entry(i, &ip_conntrack_expect_list, list) {
if (expect_matches(i, expect)) {
/* Refresh timer: if it's dying, ignore.. */
if (refresh_timer(i)) {
ret = 0;
/* We don't need the one they've given us. */
ip_conntrack_expect_free(expect);
goto out;
}
} else if (expect_clash(i, expect)) {
ret = -EBUSY;
goto out;
}
}

/* Will be over limit? */
if (expect->master->helper->max_expected && 
    expect->master->expecting >= expect->master->helper->max_expected)
evict_oldest_expect(expect->master);

ip_conntrack_expect_insert(expect);
ret = 0;
out:
WRITE_UNLOCK(&ip_conntrack_lock);
  return ret;
}

首先看是否已经有相应节点,如没有,则插入之,不同的是,这次的链表首部是ip_conntrack_expect_list。

OK,数据包
192.168.0.1:1106 -> 192.168.1.1:69 udp
接下来就进入ip_confirm,然后离开本机。

当回来的数据传输的包进入Netfliter:
192.168.1.1:1077	->	192.168.0.1:1106	udp


因为端口已经变成了1077,而不是69,所以它不会同第一条连接的repl_tuple匹配(废话,当然不匹配了,否则还用搞这么复杂),所以,当然没有属于它的连接,数据包也会进入init_conntrack,初始化一个连接:

static struct ip_conntrack_tuple_hash *

init_conntrack(const struct ip_conntrack_tuple *tuple,
       struct ip_conntrack_protocol *protocol,
       struct sk_buff *skb)
{
struct ip_conntrack_expect *exp;

……
exp = find_expectation(tuple);

if (exp) {
DEBUGP("conntrack: expectation arrives ct=%p exp=%p\n",
conntrack, exp);
/* Welcome, Mr. Bond.  We've been expecting you... */
__set_bit(IPS_EXPECTED_BIT, &conntrack->status);
conntrack->master = exp->master;
#if CONFIG_IP_NF_CONNTRACK_MARK
conntrack->mark = exp->master->mark;
#endif
nf_conntrack_get(&conntrack->master->ct_general);
CONNTRACK_STAT_INC(expect_new);
} else {
conntrack->helper = ip_ct_find_helper(&repl_tuple);

CONNTRACK_STAT_INC(new);
}
……
}

这一次,find_expectation函数根据当前数据包的tuple,查找有没有对应的expect,很幸运,我们刚才注册的expect被查到了:
static struct ip_conntrack_expect *

find_expectation(const struct ip_conntrack_tuple *tuple)
{
struct ip_conntrack_expect *i;

list_for_each_entry(i, &ip_conntrack_expect_list, list) {
/* If master is not in hash table yet (ie. packet hasn't left
   this machine yet), how can other end know about expected?
   Hence these are not the droids you are looking for (if
   master ct never got confirmed, we'd hold a reference to it
   and weird things would happen to future packets). */
if (ip_ct_tuple_mask_cmp(tuple, &i->tuple, &i->mask)
    && is_confirmed(i->master)
    && del_timer(&i->timeout)) {
unlink_expect(i);
return i;
}
}
return NULL;
}

比较函数仍然是ip_ct_tuple_mask_cmp,再来看一遍它的代码:

static inline int ip_ct_tuple_mask_cmp(const struct ip_conntrack_tuple *t,

       const struct ip_conntrack_tuple *tuple,
       const struct ip_conntrack_tuple *mask)
{
return !(((t->src.ip ^ tuple->src.ip) & mask->src.ip)
 || ((t->dst.ip ^ tuple->dst.ip) & mask->dst.ip)
 || ((t->src.u.all ^ tuple->src.u.all) & mask->src.u.all)
 || ((t->dst.u.all ^ tuple->dst.u.all) & mask->dst.u.all)
 || ((t->dst.protonum ^ tuple->dst.protonum)
     & mask->dst.protonum));
}

回忆初始化tftp的expect时,作为比较用的mask的源端口并没有被赋值:
		exp->mask.src.ip = 0xffffffff;

exp->mask.dst.ip = 0xffffffff;
exp->mask.dst.u.udp.port = 0xffff;
exp->mask.dst.protonum = 0xff;
exp->expectfn = NULL;

所以,对于这条应答的包来讲,尽管它的来源端口是1077,而不是我们希望的69,但
((t->src.u.all ^ tuple->src.u.all) & mask->src.u.all)仍然为0,所以,它仍然被查找出来了。

这样,Netfilter发现该连接有对应的expect,哈哈,终于找到你了,于是:
__set_bit(IPS_EXPECTED_BIT, &conntrack->status);


设置该连接为“关连”标志位(等回到resolve_normal_ct函数中,再将此连接设置为IP_CT_RELATED)
,这样,关连的连接就被识别出来了。并且,该连接的master指针,指向了第一条连接:
conntrack->master = exp->master;

主要的流程就这么简单!!

小结一下:
首先,特殊的协议注册一个helper,helper模块根据协议的某些特性,如(udp & dport==69),“帮助”我们发现一条连接是“特殊协议”,于是调用help函数,初始化一个“期望”连接expect,事实上,这个expect主要的作用仅仅是比较。当回来的应答包穿过Netfilter时,它被发现有一个expect,于是,它就被识别出来是一个“关联”连接了!!



  回复于:2006-10-16 11:56:53

这个tftp指的不是"Trivial File Transfer Protocol"吧?


  回复于:2006-10-16 12:04:10

引用:原帖由 albcamus 于 2006-10-16 11:56 发表
这个tftp指的不是"Trivial File Transfer Protocol"吧? 



就是它呀?它与FTP有点类似,应答包会自动打开高端口回应,而不再用69,所以,Linux就需要为它做动态协议的连接跟踪,这篇文章就是借tftp协议的实现来分析整个连接跟踪模块对于动态协议跟踪的实现!


  回复于:2006-10-16 13:00:01

引用:原帖由 独孤九贱 于 2006-10-16 12:04 发表


就是它呀?它与FTP有点类似,应答包会自动打开高端口回应,而不再用69,所以,Linux就需要为它做动态协议的连接跟踪,这篇文章就是借tftp协议的实现来分析整个连接跟踪模块对于动态协议跟踪的实现! 



谢谢,还有一个疑问:那为什么tftp需要在内核层实现呢?


  回复于:2006-10-16 13:32:56

引用:原帖由 albcamus 于 2006-10-16 13:00 发表


谢谢,还有一个疑问:那为什么tftp需要在内核层实现呢? 



嘻嘻,不是tftp协议在内核实现
是内核中实现对tftp协议的跟踪和状态检测……
《 Netfliter状态跟踪之动态协议的实现浅析(tftp实现) 》,或许这个名字本身让人误解,我想表达的意思是“Netfilter中状态跟踪模块对动态协议的实现支持,用tftp协议的跟踪实现的代码来分析”……


  回复于:2006-10-16 13:45:52

谢谢! 偶不熟悉网络, 见笑了:D


  回复于:2006-10-16 16:53:09

good,最近在研究这方面的东西,打算用conntrack实现一个ttl状态维持的东西来避免GFW强行插入的reset包,lz这篇文章刚好是最为关键的conntrack代码分析,谢了。


  回复于:2006-10-16 17:43:57

引用:原帖由 colddawn 于 2006-10-16 16:53 发表
good,最近在研究这方面的东西,打算用conntrack实现一个ttl状态维持的东西来避免GFW强行插入的reset包,lz这篇文章刚好是最为关键的conntrack代码分析,谢了。 


我记得GFW是同时向客户端与服务器发送rst的,只在客户端避免有用麽?我曾经简单的在客户端丢掉所有rst,但是服务器端还是关闭了连接。


  回复于:2006-10-16 17:52:11

引用:原帖由 mingyanguo 于 2006-10-16 17:43 发表

我记得GFW是同时向客户端与服务器发送rst的,只在客户端避免有用麽?我曾经简单的在客户端丢掉所有rst,但是服务器端还是关闭了连接。 


说的没错
我曾经做过一个实验,比如用 google 搜索敏感信息,抓包发现收到了 RST 以及 HTTP/1.0 的正文的第一个包,证明那个 RST 是某个第三者插入的,于是写了一个 module 来 DROP 所有 RST 包

实验的结果和 mingyanguo 说的一样,虽然 client 的 RST 收不到了,不会马上弹出无法连接的界面,但是由于 server 已经 disconnect 了,所以最后超时后提示错误


  回复于:2006-10-16 18:57:38

引用:原帖由 platinum 于 2006-10-16 17:52 发表

说的没错
我曾经做过一个实验,比如用 google 搜索敏感信息,抓包发现收到了 RST 以及 HTTP/1.0 的正文的第一个包,证明那个 RST 是某个第三者插入的,于是写了一个 module 来 DROP 所有 RST 包

实验的结果 ... 



你们说的什么GFW,什么Reset?听不懂,哪位能否详细讲讲?


  回复于:2006-10-16 19:27:12

引用:原帖由 独孤九贱 于 2006-10-16 18:57 发表


你们说的什么GFW,什么Reset?听不懂,哪位能否详细讲讲? 


他们说的可能是这个
http://seblog.kilu2.de/2006/05/gfw.html
但无论是内容过滤屏蔽,还是共享探测禁止,都会发送 RST 干扰包


  回复于:2006-10-16 22:12:51

单纯过滤rst当然不行,但由于FW发过来的RST包的ipttl是按照标准Unix的64递减,和通常服务器的ttl在绝大多数情况下是不同的,因此只要在conntrack中加上ttl持续性检测机制,也就是说同一个连接,对端服务器发送的所有包的ttl应该都是相同的,有不同的就认为是GFW的RST,这样就能在很大程度避免GFW的RST干扰。不过这么做是违反TCP/IP协议栈约束的,因为从IP网络的设计上,两个ip之间的通讯所有包并不一定是走同样的路由的。不过为了达到我们的目的,只能这么做了,实际情况99.9%的连接其所有包都只走一条路径,就是说对端过来的IP包的TTL是稳定的。

另外,我有这个想法是因为看了这篇文章:


[ 本帖最后由 colddawn 于 2006-10-16 22:21 编辑 ]


  回复于:2006-10-16 22:15:09

楼上说的不错,难点在于往双方发送的RST,不过在于我的业务需求主要是与国外的邮件通信,我这边做好这个东西同时通知对方服务器做好,就能尽量避免干扰,还算有一点点意义吧。


  回复于:2006-10-17 08:59:15

引用:原帖由 platinum 于 2006-10-16 19:27 发表

他们说的可能是这个
http://seblog.kilu2.de/2006/05/gfw.html
但无论是内容过滤屏蔽,还是共享探测禁止,都会发送 RST 干扰包 



我只找到这个:


原来就是它啊!!咬牙切齿中!

[ 本帖最后由 独孤九贱 于 2006-10-17 09:00 编辑 ]


  回复于:2006-10-17 17:25:41

master_conntrack
      |
      |
      expect1 <---- child_conntrack1
      |
      | 
      expect2 <---- child_conntrack2


像这种情况(即存在子连接的子连接的情况,
如ip_conntrack_helper_pptp.c中expectfn = pptp_expectfn,
  ip_conntrack_helper_h323.c中exp->expectfn = ip_conntrack_h245_expect)的情况LZ好象讲的不深入。。。
存在子连接的子连接的情况(exp->expectfn !=NULL)应该不止一层help(看看ip_conntrack_helper_pptp.c就会发现help有h245_help、q931_help、ras_help等),每多一层子连接( child_conntrack)应该多一层help。

而ip_conntrack_tftp.c和ip_conntrack_ftp.c相对来说是相当简单的了(因为此时exp->expectfn = NULL,只有1个子连接)。

希望能够对存在子连接的子连接的情况(exp->expectfn !=NULL)作一个更深入的剖析。
向LZ鞠躬了!!!


  回复于:2006-10-17 17:32:30

Linux netfilter Hacking HOWTO中有:

连接跟踪帮助模块

描述

连接跟踪模块的任务是指出哪一个信息包属于一个已经建立的连接。模块这样做有下面的含义:
· 告诉netfilter我们的模块对哪一个信息包感兴趣(大多数的帮助操作一个特殊的端口)。
· 用netfilter注册一个函数。这个函数被每一个符合上面的标准的信息包调用。
· 一个“ip_conntrack_expect_related()”函数,它能从那里被调用来告诉netfilter去期待一个关联连接。

如果这里有一些附加的工作在预期的连接的第一个信息包到达的时候被完成,模块可以注册一个在这个时候被调用的回调函数。
可用的结构和函数
你的内核模块的初始函数用一个指向“struct ip_conntrack_helper”的指针调用“ip_conntrack_helper_register()”。这个结构有下面的成员:
list 
这是链表的报头。Netfilter在内部掌管这个列表只要用“{NULL,NULL}”初始化。
name 
这是一个指向字符串常量的指针,该常量是协议名称。(“ftp”,“irc”,…)。
flags 
一组有下面一个或多个标志的标志:
· IP_CT_HELPER_F_REUSE_EXPECT重新使用期待值,如果限制(查看下面的“max_expected”)到达。
me 
一个指向帮助的模块结构的指针。用“THIS_MODULE”宏初始化它。
max_expected 
未认可的(突出的)预期值的最大数值。
timeout 
对于每一个未认可的预期值的timeout(很短时间内)。在预期被“ip_conntrack_expect_related()”函数产生片刻之后,一个预期是被注销的“timeout”。 
tuple 
这是一个“struct ip_conntrack_tuple”,它指出我们的conntrack帮助模块感兴趣的信息包。
mask 
又一个“struct ip_conntrack_tuple”,这个掩码指定哪一个数组的比特位是有效的。
help 

netfilter应该为每一个信息包匹配tuple+mask调用的函数。

一个conntrack帮助模块的框架例子


#define FOO_PORT 111

static int foo_expectfn(struct ip_conntrack *new)
{
/* called when the first packet of an expected
connection arrives */

return 0;
}

static int foo_help(const struct iphdr *iph, size_t len, 
struct ip_conntrack *ct, 
enum ip_conntrack_info ctinfo)
{
/* analyze the data passed on this connection and 
decide how related packets will look like */

/* update per master-connection private data
(session state, ...) */
ct->help.ct_foo_info = ...

if (there_will_be_new_packets_related_to_this_connection)
{
struct ip_conntrack_expect exp;

memset(&exp, 0, sizeof(exp));
exp.t = tuple_specifying_related_packets;
exp.mask = mask_for_above_tuple;
exp.expectfn = foo_expectfn;
exp.seq = tcp_sequence_number_of_expectation_cause;

/* per slave-connection private data */
exp.help.exp_foo_info = ...

ip_conntrack_expect_related(ct, &exp);
}
return NF_ACCEPT;


static struct ip_conntrack_helper foo;

static int __init init(void)
{
memset(&foo, 0, sizeof(struct ip_conntrack_helper);

foo.name = "foo";
foo.flags = IP_CT_HELPER_F_REUSE_EXPECT;
foo.me = THIS_MODULE;
foo.max_expected = 1; /* one expectation at a time */
foo.timeout = 0; /* expectation never expires */

/* we are interested in all TCP packets with destport 111 */
foo.tuple.dst.protonum = IPPROTO_TCP;
foo.tuple.dst.u.tcp.port = htons(FOO_PORT);
foo.mask.dst.protonum = 0xFFFF;
foo.mask.dst.u.tcp.port = 0xFFFF;
foo.help = foo_help;

return ip_conntrack_helper_register(&foo); 
}

static void __exit fini(void)
{
ip_conntrack_helper_unregister(&foo);
}



其中conntrack帮助模块的框架例子对我们的参考意义实在太大了
向所有伟大的人致敬!!!


  回复于:2006-10-19 13:50:57

update_nl_seq    find_nl_seq作用是什么
阅读(1558) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~