免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12
最近访问板块 发新帖
楼主: 狼族狼心
打印 上一主题 下一主题

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

论坛徽章:
0
7 [报告]
发表于 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, 下载次数: 1003)

论坛徽章:
0
6 [报告]
发表于 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
5 [报告]
发表于 2010-07-17 11:23 |只看该作者
看看

论坛徽章:
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
4 [报告]
发表于 2010-07-16 16:53 |只看该作者
谢谢分享

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

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

清除 Cookies - ChinaUnix - Archiver - WAP - TOP