• 网络协议栈(14)iptables实现浅析


    一、linux下的防火墙基础iptables
    这个功能在之前的说法叫做ipchains,后来才修改为这个名字。名字本身通常会体现出很多的信息,这些信息可能根本不需要专业知识,只是因为这些术语本身不是我们的母语,加上翻译中信息的缺失和转义等问题,导致很多术语本身的意义并没有体现出来。拿我们最为熟悉的程序设计语言来说,如果是以英语为母语,那么这些所谓的编程事实上是和我们平时说话差不多的,只是更加的官方化和形式化。这对于早期的PASCAL语言更是如此,基本的句子就是模仿英语的自然语言,C语言有些相对简化,但是也还是以英语为基础,例如for可以翻译为“对于”,而脚本语言中常见的foreach则可以跟进一步的翻译为“对于每一个”,大家可以想象在这样的环境下编程是一个什么样的感觉。
    而对于从ipchains到iptables的转换,本身通过两个单词也透露出了很多的信息。所谓的“chain”是一个链结构,或者说是我们常见的有向图结构,只是更为简单,可以认为是一个常见的有向树结构;而一个table则是一个正则的二维结构。在“编译原理”中,开始就讲了词法分析使用的自动机模型,其中最开始的结构就是一个有向图,然后转换为一个二维的矩阵,事实上在工程中,大量的有向图也的确是通过二维数字来表示的,例如我们熟知的floyd算法,dijstras算法等。
    如果是一个二维结构,那么可以认为它的第一维就是不同的功能表,例如用于做NAT转换的nat表,用于做修改的mangle表,防火墙的netfilter表等。根据linux内核一个报文经过的节点来说可以分为5个大家比较熟悉的报文拦截点,也就是
    /* IP Hooks */
    /* After promisc drops, checksum checks. */
    #define NF_IP_PRE_ROUTING    0
    /* If the packet is destined for this box. */
    #define NF_IP_LOCAL_IN        1
    /* If the packet is destined for another interface. */
    #define NF_IP_FORWARD        2
    /* Packets coming from a local process. */
    #define NF_IP_LOCAL_OUT        3
    /* Packets about to hit the wire. */
    #define NF_IP_POST_ROUTING    4
    #define NF_IP_NUMHOOKS        5
    这就是iptables中二维表的开始。通常一个表会在上面五个拦截点的多个地方都设置拦截,可以想象的就是对于一个NAT转化来说,正向和反向的转换都是要设置拦截点的。虽然这个二维结构和很多的图结构一样通常比较稀疏,但是使用这种表结构来表示对于维护、理解和扩展来说都是一种比较好的方法。
    再进一步说,对于同一个table的同一个拦截点,可以挂载不同的匹配规则,所以这个地方可以看做是以三维的结构也未尝不可。
    二、简单的iptables的例子
    网络上关于iptables的文档通过google搜索iptable出现的第一条就是http://www.frozentux.net/iptables-tutorial/cn/iptables-tutorial-cn-1.1.19.html中给出的文档,这个文档比较详细,也比较长,所以我没有看完。但是通过里面例子的说明,可以和这个iptables的结构有一个比较好的对应。刚才说了一个二维结构的例子,那么这个二维结构中保存的内容是什么呢?
    通过之前的文档可以看到,iptables参数中有两个比较重要的概念,一个是match信息,不同的table在不同的挂接点有自己的代理,那么具体用户要拦截什么样的信息是有用户态确定的,而内核只是提供功能。用户通常需要完成的功能无非是对“特定类型的报文" "执行特定的操作"。
    这个特定的报文就是报文的各个参数,例如端口号、ip地址、TTL信息,使用的协议等等,我们可以设置不同的规则来进行不同的匹配,这些通常称为match信息(我们也可以认为是一种”过滤“机制,只是没有人这么叫)。
    特定的操作这个概念就不叫宽泛,因为特定的操作往往是不确定的,事先不确定的东西,但是我们可以认为是一种”动作“,而它的术语名称为”target“。
    这个基本的模型还是很简单明了的,就像我们写程序一样,if condition dosomething一样天经地义,只是问题复杂之后模糊了它原本的意义。
    现在再反过头来看这个target,target是一些特定的动作,在文档的 6.5. Targets/Jumps中有详细说明,例如其中说明了DROP  LOG MASQUERADE REJECT RETURN等一些不同的target,这些target有不同的默认动作和意义,这一点再之后再详细说明。
    我们看文档中的一个例子
    iptables -t nat -A POSTROUTING -p tcp --dst $HTTP_IP --dport 80 -j SNAT --to-source $LAN_IP 
    其中的 t nat就是一个内核中预定义的一个iptable功能表,用于完成网络地址转换,这里可以认为是table的第一维,或者说是行内容,其中的 -A POSTROUTING体现了第二维和第三维,其中的 POSTROUTING 是在内核中五个挂接点中的第一个入口点,而A则体现了第三维,是在这个table的这个检测点上追加一项,而其它的检测项不受影响。然后的-p tcp --dst $HTTP_IP --dport 80是体现的是该规则在挂接点上的匹配规则,相当于 if condition中的condition,然后通过-j SNAT指明这次操作的目标(动作),最后的--to-source $LAN_IP 为转换的参数。
    三、内核中相关数据
    在内核中,不同的target一般有各自对应的源文件,大家在内核的linux-2.6.21 etipv4 etfilter文件夹下可以发现很多ipt_xxx.c类型的文件,这些文件每个都对应一个特定的target实现,例如ipt_LOG.c中对应的LOG target,ipt_MASQUERADE.c对应的MASQUERADE.c目标等。
    1、初始化结构
    以linux-2.6.21 etipv4 etfilteriptable_mangle.c初始化为例,其中使用的到数据结构及定义为
    linux-2.6.21includelinux etfilterx_tables.h
    #define ipt_standard_target xt_standard_target
    struct xt_standard_target
    {
        struct xt_entry_target target;
        int verdict;
    };
    struct xt_entry_target
    {
        union {
            struct {
                u_int16_t target_size;

                /* Used by userspace */
                char name[XT_FUNCTION_MAXNAMELEN-1];

                u_int8_t revision;
            } user;
            struct {
                u_int16_t target_size;

                /* Used inside the kernel */
                struct xt_target *target;
            } kernel;

            /* Total length */
            u_int16_t target_size;
        } u;

        unsigned char data[0];
    };
    /* Yes, Virginia, you have to zero the padding. */
    struct ipt_ip {
        /* Source and destination IP addr */
        struct in_addr src, dst;
        /* Mask for src and dest IP addr */
        struct in_addr smsk, dmsk;
        char iniface[IFNAMSIZ], outiface[IFNAMSIZ];
        unsigned char iniface_mask[IFNAMSIZ], outiface_mask[IFNAMSIZ];

        /* Protocol, 0 = ANY */
        u_int16_t proto;

        /* Flags word */
        u_int8_t flags;
        /* Inverse flags */
        u_int8_t invflags;
    };
    struct ipt_entry
    {
        struct ipt_ip ip;

        /* Mark with fields that we care about. */
        unsigned int nfcache;

        /* Size of ipt_entry + matches */
        u_int16_t target_offset;
        /* Size of ipt_entry + matches + target */
        u_int16_t next_offset;

        /* Back pointer */
        unsigned int comefrom;

        /* Packet and byte counters. */
        struct xt_counters counters;

        /* The matches (if any), then the target. */
        unsigned char elems[0];
    };

    /* Standard entry. */
    struct ipt_standard
    {
        struct ipt_entry entry;
        struct ipt_standard_target target;
    };
    #define MANGLE_VALID_HOOKS ((1 << NF_IP_PRE_ROUTING) |
                    (1 << NF_IP_LOCAL_IN) |
                    (1 << NF_IP_FORWARD) |
                    (1 << NF_IP_LOCAL_OUT) |
                    (1 << NF_IP_POST_ROUTING))

    /* Ouch - five different hooks? Maybe this should be a config option..... -- BC */
    static struct
    {
        struct ipt_replace repl;
        struct ipt_standard entries[5];
        struct ipt_error term;
    } initial_table __initdata
    = { { "mangle", MANGLE_VALID_HOOKS, 6,
          sizeof(struct ipt_standard) * 5 + sizeof(struct ipt_error),
          { [NF_IP_PRE_ROUTING]     = 0,
        [NF_IP_LOCAL_IN]     = sizeof(struct ipt_standard),
        [NF_IP_FORWARD]     = sizeof(struct ipt_standard) * 2,
        [NF_IP_LOCAL_OUT]     = sizeof(struct ipt_standard) * 3,
        [NF_IP_POST_ROUTING]     = sizeof(struct ipt_standard) * 4 },
          { [NF_IP_PRE_ROUTING]     = 0,
        [NF_IP_LOCAL_IN]     = sizeof(struct ipt_standard),
        [NF_IP_FORWARD]     = sizeof(struct ipt_standard) * 2,
        [NF_IP_LOCAL_OUT]     = sizeof(struct ipt_standard) * 3,
        [NF_IP_POST_ROUTING]    = sizeof(struct ipt_standard) * 4 },
          0, NULL, { } },
        {
            /* PRE_ROUTING */
            { { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 },
            0,
            sizeof(struct ipt_entry),
            sizeof(struct ipt_standard),
            0, { 0, 0 }, { } },
              { { { { IPT_ALIGN(sizeof(struct ipt_standard_target)), "" } }, { } },
            -NF_ACCEPT - 1 } },
            /* LOCAL_IN */
            { { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 },
            0,
            sizeof(struct ipt_entry),
            sizeof(struct ipt_standard),
            0, { 0, 0 }, { } },
              { { { { IPT_ALIGN(sizeof(struct ipt_standard_target)), "" } }, { } },
            -NF_ACCEPT - 1 } },
            /* FORWARD */
            { { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 },
            0,
            sizeof(struct ipt_entry),
            sizeof(struct ipt_standard),
            0, { 0, 0 }, { } },
              { { { { IPT_ALIGN(sizeof(struct ipt_standard_target)), "" } }, { } },
            -NF_ACCEPT - 1 } },
            /* LOCAL_OUT */
            { { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 },
            0,
            sizeof(struct ipt_entry),
            sizeof(struct ipt_standard),
            0, { 0, 0 }, { } },
              { { { { IPT_ALIGN(sizeof(struct ipt_standard_target)), "" } }, { } },
            -NF_ACCEPT - 1 } },
            /* POST_ROUTING */
            { { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 },
            0,
            sizeof(struct ipt_entry),
            sizeof(struct ipt_standard),
            0, { 0, 0 }, { } },
              { { { { IPT_ALIGN(sizeof(struct ipt_standard_target)), "" } }, { } },
            -NF_ACCEPT - 1 } },
        },
        /* ERROR */
        { { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 },
        0,
        sizeof(struct ipt_entry),
        sizeof(struct ipt_error),
        0, { 0, 0 }, { } },
          { { { { IPT_ALIGN(sizeof(struct ipt_error_target)), IPT_ERROR_TARGET } },
          { } },
        "ERROR"
          }
        }
    };
    大家可以看一下,这个结构的初始化非常的复杂,可以认为是蔚为壮观,主要是考验大家的眼力劲儿的。不过大家不用震惊,因为这的确只是一个最为简单原始的初始化结构,因为考虑到之前说过一个table的二维结构(或者说是三维结构),这里只是为table内的每个挂接点定义最为原始的关节点头结构,从而可以让用户态的iptables注册的各种节点能够挂接到这个地方,那么为什么定义的这么复杂呢?是因为它使用的接口是要给其它模块公用的,公用就要考虑到很多比较特殊的场景,从而看起来这个结构臃肿而诡异。
    这个结构通过
    ret = ipt_register_table(&packet_mangler, &initial_table.repl)
    注册给通用接口,从而建立自己的挂接点。这个接口以两个参数为原型,净第二个参数声明的原始头位置注册到以第一个参数为原型的xt_table结构的void *private;成员中。这一点可以在ipt_do_table函数中得到验证,代码为
        struct xt_table_info *private;
    ……
        private = table->private;
    2、注册钩子
    前面的内容虽然复杂,但是终归是一些数据结构的注册,相当于一些基础设施建设,但是没有涉及到真正的动作,而这个动作的注册就需要通过使用其中的
    static struct nf_hook_ops ipt_ops[] = {
        {
            .hook        = ipt_route_hook,
            .owner        = THIS_MODULE,
            .pf        = PF_INET,
            .hooknum    = NF_IP_PRE_ROUTING,
            .priority    = NF_IP_PRI_MANGLE,
        },
        {
            .hook        = ipt_route_hook,
            .owner        = THIS_MODULE,
            .pf        = PF_INET,
            .hooknum    = NF_IP_LOCAL_IN,
            .priority    = NF_IP_PRI_MANGLE,
        },
        {
            .hook        = ipt_route_hook,
            .owner        = THIS_MODULE,
            .pf        = PF_INET,
            .hooknum    = NF_IP_FORWARD,
            .priority    = NF_IP_PRI_MANGLE,
        },
        {
            .hook        = ipt_local_hook,
            .owner        = THIS_MODULE,
            .pf        = PF_INET,
            .hooknum    = NF_IP_LOCAL_OUT,
            .priority    = NF_IP_PRI_MANGLE,
        },
        {
            .hook        = ipt_route_hook,
            .owner        = THIS_MODULE,
            .pf        = PF_INET,
            .hooknum    = NF_IP_POST_ROUTING,
            .priority    = NF_IP_PRI_MANGLE,
        },
    };

    ret = nf_register_hooks(ipt_ops, ARRAY_SIZE(ipt_ops));
    来完成,而这里注册的钩子函数进一步再使用此处初始化的packet_mangler结构,从而完成数据和代码的汇合。例如在
    /* The work comes in here from netfilter.c. */
    static unsigned int
    ipt_route_hook(unsigned int hook,
         struct sk_buff **pskb,
         const struct net_device *in,
         const struct net_device *out,
         int (*okfn)(struct sk_buff *))
    {
        return ipt_do_table(pskb, hook, in, out, &packet_mangler);
    }
    3、执行注册
    当用户态通过iptables添加一个规则的时候,它事实上是向内核添加了一个新的struct ipt_entry实例,这个实例中包含了设置的匹配信息,保存在结构中的struct ipt_ip ip;域中;还包含了这个规则的target,通过结构中的u_int16_t target_offset;给出。其中这个ipt_entry结构比较复杂,事实上它并不是一个定长结构,它可以包含任意多的matches和一个target,而他们这些附加类型则通过结构中的
        /* Size of ipt_entry + matches */
        u_int16_t target_offset;
        /* Size of ipt_entry + matches + target */
        u_int16_t next_offset;
    来确定,所以对于这个结构的遍历并不能通过数组下标来顺序访问,而要通过特定的接口来完成。例如
    /* fn returns 0 to continue iteration */
    #define IPT_MATCH_ITERATE(e, fn, args...)   
    ({                       
        unsigned int __i;           
        int __ret = 0;               
        struct ipt_entry_match *__match;   
                           
        for (__i = sizeof(struct ipt_entry);   
             __i < (e)->target_offset;       
             __i += __match->u.match_size) {   
            __match = (void *)(e) + __i;   
                           
            __ret = fn(__match , ## args);   
            if (__ret != 0)           
                break;           
        }                   
        __ret;                   
    })
    4、ipt_do_table函数
    e = get_entry(table_base, private->hook_entry[hook]); 这里首先通过hook得到table的检测点,相当于表的第hook列,然后接下来是第三维的遍历,也就是同一检测点的多个检测功能的遍历。

        /* For return from builtin chain */
        back = get_entry(table_base, private->underflow[hook]);
    ……
    do {
            IP_NF_ASSERT(e);
            IP_NF_ASSERT(back);
            if (ip_packet_match(ip, indev, outdev, &e->ip, offset)) { 首先是ipt_ip结构中各项匹配
                struct ipt_entry_target *t;
        if (IPT_MATCH_ITERATE(e, do_match,
                              *pskb, in, out,
                              offset, &hotdrop
    ) != 0)各个附加match的遍历匹配
                    goto no_match;
    ……
    t = ipt_get_target(e);
                IP_NF_ASSERT(t->u.kernel.target);
                /* Standard target? */
                if (!t->u.kernel.target->target) { target为一个指示,例如NF_STOP、NF_DROP等各种动作
                    int v;

        } else {
                    verdict = t->u.kernel.target->target(pskb,执行内核函数,例如MASQUERADE中的masquerade_target
                                         in, out,
                                         hook,
                                         t->u.kernel.target,
                                         t->data);
    ……
    no_match:
                e = (void *)e + e->next_offset;
            }
        } while (!hotdrop);

  • 相关阅读:
    Lotus Notes中文档查询(转)
    MSSQL日志管理
    VS使用带临时表的存储过程
    TaskbarForm
    IT人士在离职后可以做的14件事情
    app.config数据库连接字符串的加密
    IT职场人,切不要一辈子靠技术生存
    wmi资料
    迁移成功
    【SpeC#】-C#的又一同胞兄弟
  • 原文地址:https://www.cnblogs.com/tsecer/p/10487381.html
Copyright © 2020-2023  润新知