Chinaunix首页 | 论坛 | 博客
  • 博客访问: 248657
  • 博文数量: 55
  • 博客积分: 2160
  • 博客等级: 大尉
  • 技术积分: 598
  • 用 户 组: 普通用户
  • 注册时间: 2010-03-08 14:36
文章分类

全部博文(55)

文章存档

2013年(1)

2012年(5)

2010年(49)

我的朋友

分类: LINUX

2010-03-08 14:54:54

1、模块的注册

源码在ip_conntrack_.c中:
 
init函数中定义了

static struct ip_conntrack_helper [MAX_PORTS];
并初始化它,并注册它:

memset(&, 0, sizeof(struct ip_conntrack_helper));
……
ret=ip_conntrack_helper_register(&);


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

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

的成员的赋初始化值的时候,我们可以对照理解struct ip_conntrack_helper结构的许多重要的成员:

.tuple.dst.protonum = IPPROTO_UDP; //
.tuple.src.u.udp.port = htons(ports); //目标端口,即69,这样,UDP:69成为认识tftp的唯一标志
.mask.dst.protonum = 0xFF; //目标地址掩码,以及下面一个源端口掩码,以做比较之用
.mask.src.u.udp.port = 0xFFFF;
.max_expected = 1; //最大expect,这是什么东东?后面会详解
.timeout = 5 * 60; /* 5 minutes */ //超时时间
.me = THIS_MODULE;
.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,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__POST_ROUTING,
.priority = NF__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__LOCAL_IN,
.priority = NF__PRI_CONNTRACK_HELPER,
};

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

2.我的例子
结合一个实际的传输来分析代码,先来看这个例子(该例取自《/详解卷一》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__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 = _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__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__helper(&repl_tuple);

CONNTRACK_STAT_INC(new);
}
……
}


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


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


ip_ct__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__helper(const struct ip_conntrack_tuple *tuple)
{
return LIST_FIND(&helpers, helper_,
struct ip_conntrack_helper *,
tuple);
}


比较函数是helper_

static inline int helper_(const struct ip_conntrack_helper *i,
const struct ip_conntrack_tuple *rtuple)
{
return ip_ct_tuple_mask_(rtuple, &i->tuple, &i->mask);
}


实际转向给了ip_ct_tuple_mask_函数:

static inline int ip_ct_tuple_mask_(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));
}


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

conntrack->helper = ip_ct__helper(&repl_tuple);


这个包继续前进,当它进入NF__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;
}


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

static int _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[_CT__ORIGINAL].tuple);
DUMP_TUPLE(&ct->tuplehash[_CT__REPLY].tuple);

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

exp->tuple = ct->tuplehash[_CT__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__hook)
ret = ip_nat__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;
}


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

exp = ip_conntrack_expect_alloc();


然后,初始化它:

exp->tuple = ct->tuplehash[_CT__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的时候了:
对于来讲,它的请求连接是:
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[_CT__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 = _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__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__helper(&repl_tuple);

CONNTRACK_STAT_INC(new);
}
……
}

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

static struct ip_conntrack_expect *
_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_(tuple, &i->tuple, &i->mask)
&& is_confirmed(i->master)
&& del_timer(&i->timeout)) {
unlink_expect(i);
return i;
}
}
return NULL;
}

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


static inline int ip_ct_tuple_mask_(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));
}

回忆初始化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函数中,再将此连接设置为_CT_RELATED
,这样,关连的连接就被识别出来了。并且,该连接的master指针,指向了第一条连接:
conntrack->master = exp->master;

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

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