Chinaunix首页 | 论坛 | 博客
  • 博客访问: 186597
  • 博文数量: 71
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 48
  • 用 户 组: 普通用户
  • 注册时间: 2013-09-27 14:13
个人简介

坐着,坐着,就胖了。。。

文章分类

全部博文(71)

文章存档

2014年(15)

2013年(56)

我的朋友

分类: LINUX

2014-01-08 13:17:04

Iptables-1.4.19.1 源码分析

版本号:1.4.19.1iptables-1.4.19.1.tar.bz2)

编译:

为了方便研究,编译时去掉了ipt6ables,和iptables-save等功能的源文件,只留下iptables功能。同时加上编译选项 -D  NO_SHARED_LIBS,如果在eclipse里自己编译的话,就先在源码包里加上-D  NO_SHARED_LIBS编译,把生成的initext.c initex4.c两个源文件拷进去。否则会提示找不到init_extensions(),和init_extensions4()函数的定义。对了,还要提前把每个模块源文件中的_init函数改为:文件名+_inti() 。大概有二三十处需要修改。

源码分析:

我一般分析源码都是通过给它传一条参数,然后分析它的执行流程来阅读源码的,我个人认为这种阅读方法效率高一点。

好了,我们开始吧。。。。。。。。

执行命令:

iptables -A INPUT -i eth0 -p tcp --syn -s 10.0.0.0/8  -d 10.1.28.184 -j ACCEPT

程序中重要的变量:

全局变量struct xtables_globals iptables_globals:

全局变量struct xtables_globals *xt_params:在初始化函数中将上面的全局变量赋给了它。在do_command4()函数中语句opts = xt_params->orig_opts。

全局链表struct xtables_match *xtables_matches:

全局链表struct xtables_target *xtables_targets:

do_command4()中定义的struct iptables_command_state cs:

 

 

 

 

初始化部分:

程序进入main函数,首先给table 赋值为”filter”,即当用户在命令行不加-t选项指明表名的情况下,iptables默认将对filter表执行操作(iptables 共有四表五链),紧接着调用函数xtables_init_all(),该函数激活ipv4功能,并注册错误处理函数basic_exit_err()给全局变量xt_params的exit_err成员。xt_params的类型为struct  xtables_globals。

接着调用init_extensions()加载match 模块,并初始化xtables_pending_matches全局链表xtables_pending_matchesxtables.c(struct xtables_match  *xtables_pending_matches;)定义)。以tcp协议模块为例,init_extensions()调用libxt_tcp_init();libxt_tcp_init()调用xtables_register_match(&tcp_match);将struct xtables_match tcp_match结构体插入到全局链表xtables_pending_matches的头部。

调用init_extensions4()则继续加载target模块和一些match模块,流程和init_extensions()函数一样。

初始化工作到这里已经完成了,接下来就是进入do_command4()函数解析参数了。

命令解析:

进入do_command4后,首先定义变量struct iptables_command_state cs;此后的命令解析,基本上是在填充这个结构体.iptables命令行解析使用了linux源码中常见的getopt_long函数。

 -A INPUT

首先调用 add_command()函数检查是否添加了取反符号(!),接着检查是否重复调用次命令。命令检查无误后将-A添加命令赋给command 变量保存。最后将链的名称INPUT交给变量chain保存。至此,-A INPUT解析完毕。-A命令保存在command变量,链名INPUT保存在变量chain中。

-i eth0:

首先检查-i后面是否有参数,接着调用set_option()函数检查是否有重复的选项(比如两次调用-i)。接着将选项至变量cs.options。然后检查选项是否带有取反,如果带有,则进一步检查选项是否支持取反。接着调用xtables_parse_interface()函数,检查网卡名字是否合法,将网卡名字赋给变量cs.fw.ip.iniface,并设置接口掩码 cs.fw.ip.iniface_mask.至此,-i eth0解析完毕。

-p tcp

首先调用set_option()函数,首先检查是否有重复选项,接着将选项并入cs.options位图变量,再检查选项是否带有取反,和选项是否支持取反。然后将协议名赋值给变量cs.protocol最后调用xtables_parse_protocol()将协议名解析为对应的整型变量,并赋值给变量cs.fw.ip.proto。该函数首先解析协议名是数字字符串的情况,不成功时调用getprotobyname()系统函数利用/proc/net/protocols系统文件解析,如果未解析,则继续利用用户自定义的数组解析。

--syn

--syn的解析比较复杂,解析过程中调用的函数比较多,有些复杂,分为两部:

第一步:加载tcpmatch模块。

首先进入command_default()函数,此时cs->target cs->matches的值都是空的,因此直接执行到加载协议的函数load_proto(cs),该函数首先调用should_load_proto(cs)函数,should_load_proto()函数中首先判断cs.protocol是否为空,接着调用find_proto()函数:find_proto(cs->protocol, XTF_DONT_LOAD, cs->options & OPT_NUMERIC, NULL)。此函数中首先将数字字符型的协议名,解析为对应的字母字符串协议名,调用xtables_find_match()函数;如若不成功,则cs->protocol就是字母型字符串,直接调用xtables_find_match()函数。此函数中首先检测传进来的协议名,接着在xtables_pending_matches链表中查找名字和协议名相同的结构体,并将找到的结构体从链表中删除,紧接着调用xtables_fully_register_pending_match(ptr);函数,传进去的参数是刚在链表中找到的名字和协议名相同的并且已经从链表中删除的结构体。在这个函数中再次调用xtables_find_match()函数,继续在上次的链表中寻找名称为本协议名的结构体,并删除。因为在链表xtables_pending_matches中名字为tcp的结构体只有一个,并且刚刚以经被删除掉,所以此次调用的返回值为空,程序返回到xtables_fully_register_pending_match()函数中,在该函数中将刚才找到的结构体插入到全局链表struct xtables_match *xtables_matches的结尾。程序返回xtables_find_match继续执行。接下来在全局链表xtables_matches中查找名称和协议名tcp相同的结构体,此时xtables_matches链表已经插入了本协议名的结构体,因此可以找到。又因为此协议的匹配模块结构体初次使用,所以其结构体成员struct xt_entry_match *m的值为空。接着判断刚刚找到的结构体的loaded值:if (ptr && !ptr->loaded),应为该结构体初始化时loaded成员值为空,并且该匹配模块结构体还未被使用过,所以loaded成员值为空。进入if的控制块代码,ptr被置空。退出此函数,返回find_proto()函数,紧接着返回should_load_proto()函数,又返回load_proto()函数。在此函数中又调用find_proto函数:

find_proto(cs->protocol, XTF_TRY_LOAD, cs->options & OPT_NUMERIC, &cs->matches)。注意此次调用find_proto函数与上次调用时参数的不同.首先,上次调用的第二个参数是XTF_DONT_LOAD,这次调用的第二个参数是XTF_TRY_LOAD;上次调用的最后一个参数是NULL,而本次调用的最后一个参数是&cs->matches。和上次进入find_proto()函数一样,接着调用xtables_find_match()函数。此函数继续在全局链表xtables_pending_matches中查找名字和协议名字相同的结构体。因为上次调用已经找到这个结构体并从链表中删除了这个结构体。并把它插入在了全局链表xtables_matches的结尾。所以,本次在该链表中找不到。接下来,开始在全局链表xtables_matches中查找,和上次调用一样,找到后,判断其成员的值。同样的,其成员的值仍为空。接下来判断if (ptr && !ptr->loaded),同样的,进入if的控制块。因为这次tryload的值为XTF_TRY_LOAD,执行语句将利用协议名找到的结构体的loaded成员值置为1.表示结构体已经被加载了。接下来进入if ( ptr  &&  matches )的控制块,将在全局链表xtables_matches中找到的结构体挂载到结构体cs的链表struct xtables_rule_match *matches 的尾部。这里,tcp的匹配模块结构体 struct xtables_match tcp_match 应经被加载到了全局链表xtables_matches和结构体变量cs中。程序返回find_proto函数,紧接着返回到load_proto()函数中,又接着返回到command_default()函数中。在command_default函数中继续执行,进入到if ( m != NULL) 的控制块,将cs->proto_used置为1。接下来填充之前找到的struct  xtables_match 结构体的struct xt_entry_match *m 变量,然后调用xs_init_match函数,xs_init_match函数调用tcp_init()函数初始化结构体struct xt_entry_match *m 变量的源、目的端口号为0XFFFF,返回command_default()函数,接着调用xtables_merge_options()函数:gl->opts=xtables_merge_options(gl->orig_opts, gl->opts, m->extra_opts, &m->option_offset)。该函数中首先分别对gl->orig_optsgl->optsm->extra数组的成员计数,然后减去opts中与orig_opts重复的部分,最后将他们合并到新申请的内存区域,并返回给gl->opts变量。程序返回到command_default()函数。optind-- ,准备重新取出 --syn命令行参数,进行解析

第二步:解析--syn

继续进入while循环,取出--syn,通过case语句,再次进入到command_default()函数,此时cs->target当然依旧为空,但cs->matches不为空,因为在上次调用时已经加载了tcpmatch模块。所以,执行xtables_option_mpcall(cs->c, cs->argv, cs->invert, m, &cs->fw);进入到xtables_option_mpcall函数内部,执行语句m->parse(c - m->option_offset, argv, invert, &m->mflags, fw, &m->m);调用相应模块的解析函数,本次调用tcp_parse()函数。进入case 3 分支,调用parse_tcp_flags(tcpinfo, "SYN,RST,ACK,FIN", "SYN", invert)解析。然后返回到xtables_option_mpcall()函数,返回到command_default()函数,至此,--syn参数解析完毕。继续返回到do_command4()函数中,准备解析下一个参数。

-s 10.0.0.0/8

程序进入case ‘s’:分支,调用set_option函数,检查是否有重复选项,是否带有取反,然后将OPT_DESTINATION合并到cs.options项。最后将10.0.0.0/8 赋给shostnetworkmask变量储存起来。至此,-s 10.0.0.0/8解析完毕。

-d 10.1.28.184

程序进入case ‘d’ 分支,调用set_optinon函数,检查是否有重复选项,是否带有取反,然后将OPT_SOURCE合并到cs.option项。最后将10.1.28.184赋给dhostnetworkmask变量存储起来,至此,-d 10.1.28.184解析完毕。

-j ACCEPT

程序进入case ‘j’分支,调用command_jump(&cs)函数,进入此函数后,调用set_option()函数,将OPT_JUMP合并的cs->options,然后调用parse_target(optarg)检查-j后面参数的合法性(检查长度是否超出范围,是否有空格等),最后将target赋值给cs->jumpto,即将ACCEPT赋值给cs->jumpto。返回command_jump()函数。接着调用cs->target = xtables_find_target(cs->jumpto, XTF_TRY_LOAD),解析target,刚进入函数,首先判断target是不是标准target,本次解析的targetACCEPT,是标准target。此种情况下,将cs->jumpto的名字改为standard。然后在全局链表struct xtables_target *xtables_pending_targets中寻找名字为standradstruct xtables_target结构体,找到后将此结构体从该全局链表中删除。接着调用xtables_fully_register_pending_target(ptr),在这个函数中调用old = xtables_find_target(me->name, XTF_DURING_LOAD),继续在全局链表中寻找名字为standrad的结构体,因为上次找到后,已从全局链表中删除了。所以这次没找到,返回NULL

回到xtables_fully_register_pending_target函数,将第一次找到的struct xtables_target插入到全局链表struct xtables_target *xtables_targets的首部,并初始刚找到的结构体的ttflags字段。之后程序返回xtables_find_target()函数,接着在全局链表xtables_targets中寻找名称和standrad相同的结构体,因为之前已经将刚刚找到的结构体插入到了全局链表xtables_targets。所以,自然可以找到。将找到的结构体赋值给变量ptr。将ptr->used成员置为1,退出xtables_find_target函数,返回到commadn_jump()函数,将找到的struct xtables_target结构体赋值给cs->target变量,接着给cs->target变量的struct xt_entry_target *t成员分配空间并初始化一些成员变量。接着调用xs_init_target()函数来执行对应模块的初始化函数,然后调用opts = xtables_merge_options(iptables_globals.orig_opts, opts, cs->target->extra_opts, &cs->target->option_offset);将cs->target->extra_opts合并到iptables_globals.opts中。到此-j ACCEPT解析完毕。

While循环执行完毕。

参数解析完毕。

用户数据提交:

参数解析完毕后,程序退回do_command4()函数,接着调用xtables_option_mfcall()xtables_optiom_tfcall()函数分别检查match结构体和target结构体的合法性。接着调用xtables_ipparse_multiple(shostnetworkmask, &saddrs, &smasks, &nsaddrs),解析源地址,然后解析目的地址。又跟着调用generic_opt_check(command, cs.options),检查commandcs.options的搭配是否合法。返回后,调用*handle = iptc_init(*table);从内核获取该表的规则的快照,然后继续检查cs其他成员的值的合法性,合法后,调用e = generate_entry(&cs.fw, cs.matches, cs.target->t); 使用cs.fwcs.matches以及cs.target构造内核使用的struct ipt_entry结构体。最后调用append_entry(chain, e, nsaddrs, saddrs, smasks, ndaddrs, daddrs, dmasks, cs.options&OPT_VERBOSE, *handle);完善struct ipt_entry结构体并通过libiptc库函数调用iptc_append_entry(chain, fw, handle);将信息提交到内核。然后退回到main函数,调用iptc_commit(handle);上一个libiptc库函数调用将用户修改信息提交到了内核,只有在这个函数被调用后,用户修改才真正写入内核。程序执行完毕。

阅读(4470) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~