免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 27689 | 回复: 16
打印 上一主题 下一主题

[网络子系统] Netfilter机制分析 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2010-07-16 16:47 |只看该作者 |倒序浏览
本帖最后由 狼族狼心 于 2010-07-21 11:09 编辑

最近研究了一下Netfilter,也做个总结,总结中参考了几位大侠前面发表的文章:独孤九贱,Minit,godbach等,在此先谢谢大家!
文中有些内容是直接对上面几位的Copy,内中注明了出处,希望小小心得能对大家有所帮助!


目 录
1        前言        4
2        学习框架划分        5
3        钩子函数的注册管理        6
3.1        hook的存储机制        6
3.2        hook的管理机制        7
4        规则表的存储管理        8
4.1        规则表的存储机制        8
4.2        规则的遍历机制        15
4.3        规则表的管理        16
4.3.1        注册        16
4.3.2        查找        17
4.3.3        检测        18
4.3.4        替换        22
4.4        iptables的实现机制        22
4.4.1        命令格式        23
4.4.2        命令解析        23
4.4.3        获取内核规则表        27
4.4.4        按命令进行处理        33
4.4.5        规则表提交到内核        34
4.4.6        内核处理规则表        34
5        Netfilter执行流程        37
5.1.1        钩子函数和挂载点关系        37
5.1.2        执行中函数调用关系        37
6        参考文献        39


1        前言
本文的参考分析的源代码版本是2.6.15,我是边学习边总结,学习的过程中得益于Linux论坛(http://linux.chinaunix.net/bbs/)上大侠们总结分析的文档,他山之石可以攻玉,学习过程中我也会边学边总结,开源的发展在于共享,我也抛块砖,望能引到玉!
由于自身水平有限,且相关的参考资料较少,因此其中的结论不能保证完全正确,如果在阅读本文的过程中发现了问题欢迎及时与作者联系。也希望能有机会和大家多多交流学习心得!
        学习netfilter主要参考了Linux论坛几位大侠的文档,他们分别是:独孤九贱,Godbach,Minitab;文档中参考处或者引用处,我会用紫色注明;

2        学习框架划分
我把Netfilter学习分为三块:
♠  钩子函数的注册管理;
♠  规则表的存储管理;
♠  Netfilter执行流程;
说明:
♠  钩子函数的注册管理:
主要说明Netfilter钩子函数的挂载点,hook函数的保存机制,注册方式;
♠  规则表的存储管理;
1.        主要描述规则表的存储机制,表,匹配,动作(table,match,target)三者之间的关系;
2.        规则表一些管理操作;
3.        用户态iptables的实现原理;
♠  Netfilter执行流程;
通过ip报文的转发执行过程,描述netfilter的执行流,阐述netfilter钩子函数与规则表之间的联系,以及在Ip报文处理过程中的应用;这三块可以用一下图来简单描述:

图2.1 Netfilter的三部分关系
3        钩子函数的注册管理
3.1        hook的存储机制
钩子函数由一个全局二维链表数组nf_hooks 保存,其按照协议族归类存储,在每个协
议族中,根据钩子点顺序排列,在钩子点内则根据钩子函数的优先级依次排列。钩子函数的
存储图如下图3-1所示,链表中的每个节点都是一个nf_hook_ops 结构,nf_hook_ops 实际存
储了钩子函数的内容,其结构如图3-2 所示。在相应的钩子点调用钩子函数时,则根据协议
族和钩子点找到相应的链表入口,然后依次调用该链中的每一个钩子函数对数据包进行操
作。

图3-1 钩子函数的全局存储

图3-2 钩子函数的链表
3.2        hook的管理机制
如果需要在相应的钩子点挂载钩子函数,则需要首先定义一个nf_hook_ops 结构,在其
中实现实际的钩子函数,再调用函数nf_register_hook()将该钩子函数注册到图3-1 所示的二
维链表中,nf_register_hook()函数的定义如下:


4        规则表的存储管理
4.1        规则表的存储机制
(以下内容参考:http://linux.chinaunix.net/bbs/viewthread.php?tid=1054981
规则表(也可以叫规则集)在内核是顺序存储的,涉及到的结构体大概有这么几个:
ipt_table、ipt _table_info、ipt_entry、ipt_entry_matches、ipt_entry_target;这几个结构之间的包含关系如下图:

图4-1:规则集各个结构体之间的关系
各个结构体具体内容如下:
1.        Struct ipt_table


2.        struct ipt_table_info


3.        struct ipt_entry


4.        struct ipt_entry_match


5.        struct ipt_entry_target


6.        struct ipt_match


7.        struct ipt_target

各个结构具体内容包含关系如下图:

图4-2 规则集结构体内容关系

在 Netfilter 中规则是顺序存储的,一条规则主要包括三个部分:ipt_entry、ipt_entry_matches、ipt_entry_target。ipt_entry_matches 由多个ipt_entry_match组成,ipt_entry结构主要保存标准匹配的内容,ipt_entry_match 结构主要保存扩展匹配的内容,ipt_entry_target 结构主要保存规则的动作。在ipt_entry 中还保存有与遍历规则相关的变量target_offset 与next_offset,通过target_offset 可以找到规则中动作部分ipt_entry_target 的位置,通过next_offset可以找到下一条规则的位置。规则的存储如下图4-3 所示:

图4-3 规则的存储
ipt_entry 结构如下图4-4 所示,其成员ip 指向结构ipt_ip,该结构主要保存规则中标
准匹配的内容(IP、mask、interface、proto 等),target_offset 的值等于ipt_entry 的长度与
ipt_entry_matches 的长度之和,next_offset 的值等于规则中三个部分的长度之和。通过
target_offset与next_offset可以实现规则的遍历。

图4-4 ipt_entry结构
ipt_entry_match 主要保存规则中扩展匹配内容(tos、ttl、time 等),其是Netfilter 中内
核与用户态交互的关键数据结构,在其内核部分由一个函数指针指向一个ipt_match结构,
该结构体中包含了对包做匹配的函数,是真正对包做匹配的地方。ipt_entry_target 结构与
ipt_entry_match结构很类似。

图4-5 ipt_entry_match结构

图4-6 ipt_entry_target 结构
4.2        规则的遍历机制
在 Netfilter 中,函数ipt_do_table()实现了规则的遍历(该函数的具体分析见5 Netfilter执行流程),这里主要针对遍历的几个关键点说明;
1.        查找到规则起点:
该函数根据传入的参数table 和hook找到相应的规则起点,即第一个ipt_entry的位置,主要通过函数get_entry()实现。

2.        匹配每条规则,处理相应动作:
标准匹配是通过函数ip_packet_match()实现的,该函数主要对包的五元组信息进行匹
配,扩展匹配则通过宏IPT_MATCH_ITERATE 实现,该宏的定义分析如下:

宏IPT_MATCH_ITERATE 依次调用各个ipt_entry_match所指向的ipt_match中match()处理数据包,在for 循环中使用了terget_offset位置变量查找match的位置。
在对数据包进行了匹配后,接着需要进行相应的动作处理,通过函数ipt_get_target()获取规则动作ipt_entry_target的位置:

如果还需要继续遍历下一条规则,则继续执行以下语句以找到下一条规则的开始位置:


4.3        规则表的管理
4.3.1        注册
表,匹配,动作的注册函数分别是:ipt_register_table、ipt_register_match、ipt_register_target;他们的实现在在ip_tables.c中;
每一个表,匹配,动作的注册都是以单独模块来实现的,即如果用户定义一条新的规则,提交到内核中后,内核通过检查会发现,没有该项规则,则根据注册名字分别申请模块,分别进行相应的表,匹配,动作的创建;
如函数do_replace中的代码:

上面三个注册函数实现比较简单,可以参考:
http://linux.chinaunix.net/bbs/viewthread.php?tid=1054981
4.3.2        查找
规则集在内核中顺序存放,具体规则的查找是遍历规则集,通过匹配给定的关键字找到目标规则;函数find_match即实现该功能,源码如下:

其中list_for_each_entry是一个宏,具体定义如下:


4.3.3        检测
检测主要有函数translate_table函数来完成,该函数源码分析如下:



该函数完成了两个功能:
1.        规则表的检查;检查分为两种:一种是基本检查(基本检测主要是基于IP报文五元组的检查),第二种是规则自身的检查,即通过宏IPT_ENTRY_ITERATE间接调用check_entry实现遍历检查每一个规则,check_entry源码如下:

规则内容具体的检查包括两个方面:
1)        对每个匹配的检查,通过宏IPT_MATCH_ITERATE调用函数check_match;
2)        对动作进行检查,如果没有自定义的动作,则调用标准模板的检查函数standard_check;否则调用动作自身的检查函数t->u.kernel.target->checkentry;
下面具体分析下其中的宏IPT_MATCH_ITERATE和check_match:
IPT_MATCH_ITERATE源码分析见(4.2):

check_match源码分析:

函数translate_table在检查完毕后还做了一件事情:Hook_entries和underflows的复制,在函数:check_entry_size_and_hooks中完成该功能,而该函数是通过宏IPT_ENTRY_ITERATE间接调用;check_entry_size_and_hooks源码分析如下:

4.3.4        替换
原理很简单,分两步:1. 获取老的规则集;2. 用给定的新规则集替换老的;
实现函数:replace_table,源码如下:

论坛徽章:
0
2 [报告]
发表于 2010-07-16 16:49 |只看该作者
谢谢分享,下一份看看。

论坛徽章:
324
射手座
日期:2013-08-23 12:04:38射手座
日期:2013-08-23 16:18:12未羊
日期:2013-08-30 14:33:15水瓶座
日期:2013-09-02 16:44:31摩羯座
日期:2013-09-25 09:33:52双子座
日期:2013-09-26 12:21:10金牛座
日期:2013-10-14 09:08:49申猴
日期:2013-10-16 13:09:43子鼠
日期:2013-10-17 23:23:19射手座
日期:2013-10-18 13:00:27金牛座
日期:2013-10-18 15:47:57午马
日期:2013-10-18 21:43:38
3 [报告]
发表于 2010-07-16 16:53 |只看该作者
谢谢分享

论坛徽章:
0
4 [报告]
发表于 2010-07-17 11:23 |只看该作者
看看

论坛徽章:
0
5 [报告]
发表于 2010-07-21 10:58 |只看该作者
本帖最后由 狼族狼心 于 2010-07-21 11:01 编辑

4.4        iptables的实现机制
这里分析的iptables版本是1.3.6,iptables作为用户态一个进程,入口函数比较简单,非常清晰明了,添加注释如下:

这里可以看到其中的do_command是处理的核心函数;
4.4.1        命令格式
如允许所有包通过:
#iptables  –p  input  accept
具体的命令格式不在累赘,可网上查询;
4.4.2        命令解析
命令行敲入命令后需要解析,解析在函数do_command中进行;
具体源码分析如下:(蓝色为代码标注)
(该分析引自:http://linux.chinaunix.net/bbs/viewthread.php?tid=663849
do_command 函数是整个系统的核心,负责处理整个用户的输入命令。函数首先对一些结构、变量进行初始化,初始化完毕后,进入while循环,分析用户输入的命令,设置相关的标志变量,然后根据相应标志,调用对应的处理函数。
struct ipt_entry fw, *e = NULL;
        int invert = 0;
        unsigned int nsaddrs = 0, ndaddrs = 0;
        struct in_addr *saddrs = NULL, *daddrs = NULL;
        int c, verbose = 0;
        const char *chain = NULL;
        const char *shostnetworkmask = NULL, *dhostnetworkmask = NULL;
        const char *policy = NULL, *newname = NULL;
        unsigned int rulenum = 0, options = 0, command = 0;
        const char *pcnt = NULL, *bcnt = NULL;
        int ret = 1;
        struct iptables_match *m;
        struct iptables_target *target = NULL;
        struct iptables_target *t;
        const char *jumpto = "";
        char *protocol = NULL;
        const char *modprobe = NULL;
        /*初始化变量*/
memset(&fw, 0, sizeof(fw));
        opts = original_opts;
        global_option_offset = 0;
        /* re-set optind to 0 in case do_command gets called
         * a second time */
        optind = 0;
        /*初始化两个全局变量*/
/* clear mflags in case do_command gets called a second time
         * (we clear the global list of all matches for security)*/
        for (m = iptables_matches; m; m = m->next) {
                m->mflags = 0;
                m->used = 0;
        }
        for (t = iptables_targets; t; t = t->next) {
                t->tflags = 0;
                t->used = 0;
        }
ps:开头一大堆的变量定义和初始化,可以在程序分析的时候看它们的作用,有两个全局结构变量很重要:iptables_matches和iptables_targets。现在来分析他们的作用会有一点困难,因为它们涉及到了太多方面的东东,这里,可以先把它们“想像成”用户空间用来读取内核规则的结构(当然,这有点错误)。
/*开始化析命令行*/
while ((c = getopt_long(argc, argv,
          "-A:C:R:I:L::M:F::Z::N:X::E:Vh:p:s:d:j:i:fbvnt:mc:",
                                           opts, NULL)) != -1)
{
}
这个while循环处理所有的用户输入,对应规则输出-L,有:
case 'L':
add_command(&command, CMD_LIST, CMD_ZERO,
invert);
if (optarg) chain = optarg;
else if (optind < argc && argv[optind][0] != '-'
&& argv[optind][0] != '!')
chain = argv[optind++];
break;

add_command函数负责将命令标志变量command与令标志 CMD_LIST求&运算, CMD_ZERO只是一个附加的判断标志而已,invert);然后,从命令行中取得要显示的链名(如果有的话)。
与此相关的还有用t参数指定了表名:
                case 't':
                        if (invert)
                                exit_error(PARAMETER_PROBLEM,
                                           "unexpected ! flag before --table";
                        *table = argv[optind-1];
                        break;
即,如果有’t’参数,则取’t’后跟的表名:*table = argv[optind-1],否则,它应该是主函数中默认的filter表。

命令处理完毕后,即进入执行模块:
/*因为程序定义了共享库的话,iptables_matches/iptables_target这两个结构运行至此是NULL,并且target也是NULL,对于规则显示而言,这一部份的处理目前没有实际意义,回过头再来看这一段更易理解。final_check成员函数的作用是作最终的标志检查,如果检测失则,则退出*/
        for (m = iptables_matches; m; m = m->next) {
                if (!m->used)
                        continue;
                m->final_check(m->mflags);
        }
        if (target)
                target->final_check(target->tflags);
接着对参数作一些必要的合法性检查:
        /* Fix me: must put inverse options checking here --MN */
        if (optind < argc)
                exit_error(PARAMETER_PROBLEM,
                           "unknown arguments found on commandline";
        if (!command)
                exit_error(PARAMETER_PROBLEM, "no command specified";
        if (invert)
                exit_error(PARAMETER_PROBLEM,
                           "nothing appropriate following !";
/*对于如果要进行(CMD_REPLACE | CMD_INSERT | CMD_DELETE | CMD_APPEND)处理来说,如果没有设置来源/目的地址及掩码,则给予它们一个默认值*/
        if (command & (CMD_REPLACE | CMD_INSERT | CMD_DELETE | CMD_APPEND)) {
                if (!(options & OPT_DESTINATION))
                        dhostnetworkmask = "0.0.0.0/0";
                if (!(options & OPT_SOURCE))
                        shostnetworkmask = "0.0.0.0/0";
        }
/*对来源/目的地址及掩码进行拆分,它们总是以 addr/mask的形式来出现的,根据’/’前面的字符串取得地址值,根据’/’后面的掩码位数,求得正确的掩码值,值得注意的是,同时要处理主机地址和网络地址的情况*/
        if (shostnetworkmask)
                parse_hostnetworkmask(shostnetworkmask, &saddrs,
                                      &(fw.ip.smsk), &nsaddrs);
        if (dhostnetworkmask)
                parse_hostnetworkmask(dhostnetworkmask, &daddrs,
                                      &(fw.ip.dmsk), &ndaddrs);
/*然后检查来源/目的网络地址的合法性*/
        if ((nsaddrs > 1 || ndaddrs > 1) &&
            (fw.ip.invflags & (IPT_INV_SRCIP | IPT_INV_DSTIP)))
                exit_error(PARAMETER_PROBLEM, "! not allowed with multiple"
                           " source or destination IP addresses";
/*对命令行格式进行合法性检查*/
generic_opt_check(command, options);

以上代码可以看出,do_command函数完成对命令行的解析,并且做一些合法性检查;命令输入正确并解析完毕后,下一步是获取内核的所有规则;
4.4.3        获取内核规则表
源码解析如下:
(该分析引自:http://linux.chinaunix.net/bbs/viewthread.php?tid=663849
do_command函数最后一个参数handle,是一个指向了具体表,如filter、nat表的句柄,这里判断,如果handle为空,则调用iptc_init,根据table的名称,让handle指针指向相应的表的地址空间,也就是把对应表的所有信息从内核中取出来:
        /* only allocate handle if we weren't called with a handle */
        if (!*handle)
                *handle = iptc_init(*table);
        /*如果获取换败,将试着插入模块,再次获取*/
        if (!*handle) {
                /* try to insmod the module if iptc_init failed */
                iptables_insmod("ip_tables", modprobe);
                *handle = iptc_init(*table);
        /*仍然失败,则退出*/
        if (!*handle)
                exit_error(VERSION_PROBLEM,
                           "can't initialize iptables table `%s': %s",
                           *table, iptc_strerror(errno));
/*继续进行一些简单的判断*/
if (command == CMD_APPEND
            || command == CMD_DELETE
            || command == CMD_INSERT
            || command == CMD_REPLACE) {
                /*List命令不在判断之列,暂时不分析*/
        }

补充:
获取内核规则表的函数是iptc_init,该函数采用了转定义,具体的实现为TC_INIT,在文件libiptc.c中;下面具体对该函数进行分析:
(代码分析摘自:http://linux.chinaunix.net/bbs/viewthread.php?tid=663849
再回到iptc_init 函数上来,它根据表名,从内核获取对应的表的相关信息,handle是一个iptc_handle_t类型的指针,在libiptc.c中,有如下定义:
/* Transparent handle type. */
typedef struct iptc_handle *iptc_handle_t;
在Libip4tc中:
#define STRUCT_TC_HANDLE        struct iptc_handle
在Libiptc.c中,可以找到STRUCT_TC_HANDLE的定义:
STRUCT_TC_HANDLE
{
        /* Have changes been made? */
        int changed;
        /* Size in here reflects original state. */
        STRUCT_GETINFO info;

        struct counter_map *counter_map;
        /* Array of hook names */
        const char **hooknames;
        /* Cached position of chain heads (NULL = no cache). */
        unsigned int cache_num_chains;
        unsigned int cache_num_builtins;
        /* Rule iterator: terminal rule */
        STRUCT_ENTRY *cache_rule_end;
        /* Number in here reflects current state. */
        unsigned int new_number;
        STRUCT_GET_ENTRIES entries;
};
再来看看iptc_init函数,同样在在Libip4tc中,有如下定义:
#define TC_INIT                        iptc_init
在Libiptc.c中,可以看到函数的实现,基本上iptables与内核的交互,都是使用setsockopt函数来实现的,对于获取取规是信息来说,标志位是SO_GET_INFO,而从内核返回回来的规则信息是一个STRUCT_GETINFO结构:
TC_HANDLE_T TC_INIT(const char *tablename)
{
        TC_HANDLE_T h;
        STRUCT_GETINFO info;
        unsigned int i;
        int tmp;
        socklen_t s;
        iptc_fn = TC_INIT;
        if (sockfd != -1)
                close(sockfd);
        /*为获取信息打开一个套接字接口*/
        sockfd = socket(TC_AF, SOCK_RAW, IPPROTO_RAW);
        if (sockfd < 0)
                return NULL;
        s = sizeof(info);
        if (strlen(tablename) >= TABLE_MAXNAMELEN) {
                errno = EINVAL;
                return NULL;
        }
        strcpy(info.name, tablename);
/*获取规则信息*/
/*-- yangxh mark:第一次获取表的一些大概信息,info结构,此时并没有获取具体的规则--*/
        if (getsockopt(sockfd, TC_IPPROTO, SO_GET_INFO, &info, &s) < 0)
                return NULL;
/*-- yangxh mark:在第一次获取基础上知道了规则数以及总大小,此处申请空间存放规则,地址空间的地址保存在h->entries --*/
        if ((h = alloc_handle(info.name, info.size, info.num_entries))
            == NULL)
                return NULL;
/* Too hard --RR */
#if 0
        sprintf(pathname, "%s/%s", IPT_LIB_DIR, info.name);
        dynlib = dlopen(pathname, RTLD_NOW);
        if (!dynlib) {
                errno = ENOENT;
                return NULL;
        }
        h->hooknames = dlsym(dynlib, "hooknames";
        if (!h->hooknames) {
                errno = ENOENT;
                return NULL;
        }
#else
        h->hooknames = hooknames;
#endif
        /* Initialize current state */
        h->info = info;
        h->new_number = h->info.num_entries;
        for (i = 0; i < h->info.num_entries; i++)
                h->counter_map
                        = ((struct counter_map){COUNTER_MAP_NORMAL_MAP, i});
        h->entries.size = h->info.size;
        tmp = sizeof(STRUCT_GET_ENTRIES) + h->info.size;
/*--yangxh mark:此处才是真正获取具体的规则,规则内容存放在h->entries ,大小为tmp --*/
        if (getsockopt(sockfd, TC_IPPROTO, SO_GET_ENTRIES, &h->entries,
                       &tmp) < 0) {
                free(h);
                return NULL;
        }
        CHECK(h);
        return h;
}
        函数为h分配空间,然后赋予相应的值。要理解这个函数,还需要了解STRUCT_GETINFO结构和分配内存空间的函数alloc_handle。
#define STRUCT_GETINFO                struct ipt_getinfo
/* The argument to IPT_SO_GET_INFO */
struct ipt_getinfo
{
        /* Which table: caller fills this in. */
        char name[IPT_TABLE_MAXNAMELEN];
        /* Kernel fills these in. */
        /* Which hook entry points are valid: bitmask */
        unsigned int valid_hooks;
        /* Hook entry points: one per netfilter hook. */
        unsigned int hook_entry[NF_IP_NUMHOOKS];
        /* Underflow points. */
        unsigned int underflow[NF_IP_NUMHOOKS];
        /* Number of entries */
        unsigned int num_entries;
        /* Size of entries. */
        unsigned int size;
};
/* Allocate handle of given size */
static TC_HANDLE_T
alloc_handle(const char *tablename, unsigned int size, unsigned int num_rules)
{
        size_t len;
        TC_HANDLE_T h;
        len = sizeof(STRUCT_TC_HANDLE)
                + size
                + num_rules * sizeof(struct counter_map);
        if ((h = malloc(len)) == NULL) {
                errno = ENOMEM;
                return NULL;
        }
        h->changed = 0;
        h->cache_num_chains = 0;
        h->cache_chain_heads = NULL;
        h->counter_map = (void *)h
                + sizeof(STRUCT_TC_HANDLE)
                + size;
        strcpy(h->info.name, tablename);
        strcpy(h->entries.name, tablename);
        return h;
}

补充:
1.        最终获取内核的规则是通过getsockopt函数实现的;
2.        调用了两次getsockopt函数,第一次是获取大概信息,主要是得到名字,规则数,以及占用空间大小,以便申请存储空间用;
3.        在iptables与内核的交互通过getsockopt实现,并通过指令(如SO_GET_INFO,该指令经过转定义,对于ip4来说iptables在libip4tc.c中转定义,内核对应的指令为IPT_SO_GET_INFO)来对应具体的操作,内核对应的处理函数为do_ipt_get_ctl和do_ipt_set_ctl,在ip_tables.c中;
4.4.4        按命令进行处理
http://blog.chinaunix.net/u/33048/showart_1121090.html
这部分也是do_command函数的最后。用一个swicth(command)来判断具体执行什么动作。我们这里 command=CMD_APPEND,因此调用append_entry函数来将该条iptables规则加入进去。大致介绍一下 append_entry函数的功能:
源码中具体对应的函数名为TC_APPEND_ENTRY()。该函数首先调用find_label找到整个规则中指定chain的struct chain_cache结构,然后做一下target的映射。真正执行添加规则的是insert_rules函数。该函数找到插入规则的entry点,并将该规则插入。
调整后的所有的规则都保存在结构体指针handle之中。
该逻辑结构比较简单,不在具体分析;
4.4.5        规则表提交到内核
整个do_command执行完后,也就完成了命令的解析,内核规则表的获取,具体动作的执行,下面将根据do_command处理结构决定是否需要将规则表提交到内核,如命令是现实所有规则,并不对规则进行修改,则不需要提交到内核;如果对规则进行了增删改,则需要提交;
提交规则到内核调用的函数是:iptc_commit;
iptc_commit的实现在libiptc.c中的TC_COMMIT;其中主要是通过setsockopt与内核交互,完成规则表的提交;
4.4.6        内核处理规则表
用户态最终通过setsockopt/ getsockopt与内核交互,内核对应的处理函数是do_ipt_set_ctl/ do_ipt_get_ctl;
do_ipt_get_ctl主要是获取规则,这里不讨论,主要看do_ipt_set_ctl,用户态提交规则表后内核态的处理过程;
函数do_ipt_set_ctl源码如下:

目前有两个分支,我们主要讨论IPT_SO_SET_REPLACE分支:将用户态提交的规则表,替换内核原有的规则表;即主要调用函数do_replace,该函数源码如下:



由以上源码可以看到,该函数的处理过程有四个:
1.        从用户空间将将新的规则表拷贝到内核空间;注意这里有两次拷贝(copy_from_user函数),第一次拷贝是大概信息,即ipt_replace结构,第二次才是拷贝真正的规则表信息;
2.        对新规则进行合法性检查(translate_table);
3.        根据规则表名字找到原来旧的规则表(find_table_lock);如果查不到则表示该规则是新建的,申请新的规则处理模块(try_then_request_module request_module);
4.        新的规则表替换旧的规则表(replace_table);
5.        对旧规则表进行清理释放(IPT_ENTRY_ITERATE, vfree);
6.        将结果返回给用户空间(copy_to_user);
**这其中有两个重点的函数需要关注:translate_table(见4..3.3),replace_table(见4..3.4)

论坛徽章:
0
6 [报告]
发表于 2010-07-21 11:02 |只看该作者
本帖最后由 狼族狼心 于 2010-07-21 11:03 编辑

5        Netfilter执行流程
5.1.1        钩子函数和挂载点关系
在Netfilter中的不同钩子点调用了不同的钩子函数,这些钩子函数与各个挂载点的关系如下图所示:

图5-1 Netfilter中hook函数与各挂载点关系
Netfilter 中默认表filter 在建立时则在NF_IP_LOCAL_IN,NF_IP_FORWARD 钩子点注册了钩子函数ipt_hook() , 在NF_IP_LOCAL_OUT 这个点注册了钩子函数ipt_local_out_hook(),两个钩子函数都会调用ipt_do_table()对相对应的表和钩子点的规则进
行遍历。
5.1.2        执行中函数调用关系
IP报文在转发过程中可能会流经5个hook点:NF_IP_PRE_ROUTING、NF_IP_FORWARD、NF_IP_LOCAL_IN、NF_IP_LOCAL_OUT、NF_IP_POST_ROUTING,内核的报文处理函数分别是:ip_rcv、ip_forward、ip_local_deliver、ip_build_and_send_pkt、ip_finish_output;关于报文的转发流程可以参考:
http://www.ibm.com/developerworks/cn/linux/l-ntflt/
http://linux.chinaunix.net/bbs/archiver/?tid-1132965.html
IP报文在处理过程中是怎样与netfilter的钩子函数挂钩的呢?又是怎样找到具体的规则表进行欲行定义好的动作处理?整个函数的调用过程可以见下图(我们以filter表为例,它在NF_IP_LOCAL_IN、NF_IP_FORWARD、NF_IP_LOCAL_OUT这三个点注册了函数,对应的hook函数分别为ipt_hook、ipt_hook、ipt_local_out_hook):

图5-2 Netfilter执行过程中的函数调用

6        参考文献
1.        iptables 源码分析 http://linux.chinaunix.net/bbs/viewthread.php?tid=663849
2.        iptables执行的流程分析: http://blog.chinaunix.net/u/33048/showart_1121090.html
3.        Netfilter实现机制分析: http://linux.chinaunix.net/bbs/viewthread.php?tid=1054981
4.        端木隐博客: http://blog.chinaunix.net/u/12313/article_21496.html
5.        Linux netfilter实现机制以及扩展技术:http://www.ibm.com/developerworks/cn/linux/l-ntflt/


为了方便大家下载,贴个附件
Linux-Netfilter机制分析.pdf (1.64 MB, 下载次数: 999)

论坛徽章:
0
7 [报告]
发表于 2010-07-21 14:13 |只看该作者
建议管理员设置为精华!

论坛徽章:
0
8 [报告]
发表于 2010-07-21 17:18 |只看该作者
帮顶一下,建议设精。

论坛徽章:
0
9 [报告]
发表于 2010-07-21 23:39 |只看该作者
支持下

论坛徽章:
0
10 [报告]
发表于 2010-07-22 04:51 |只看该作者
内容多,希望能再接再厉,整理一下格式。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP