大量存在于iptables模块中的BUG

iptables的maual的BUG一节:
BUGS
Bugs? What's this? ;-) Well, you might want to have a look at http://bugzilla.netfilter.org/

OK,我去netfilter的bug站点...唉,这帮人啊!
我相信很多人都遇到过iptables规则无法删除的问题,比如我使用了xtables-addons中的condition这个match模块,当我成功设置了一个规则后:
iptables -t mangle -A PREROUTING -m condition --condition xtt -j ACCEPT
然后尝试用上述规则的D命令删除之:
iptables -t mangle -D PREROUTING -m condition --condition xtt -j ACCEPT
我得到了一个报错:
iptables: Bad rule (does a matching rule exist in that chain?).
然而如果我用rulenum的方式则可以成功删除!即先查找上述规则的rulenum,然后删除该num标示的规则就可以成功,但是这样就多了一个步骤,我将不能仅仅通过简单的A或者D控制一条规则的增加和删除,于是,我必须找出来到底是哪里出了问题。

iptables规则的布局

iptables规则保存在哪里呢?答案是保存在内核里面,并且一条规则处在一个连续的地址空间,布局大致如下:
元数据|match1|match2|...|target
注意,用户态是不保存规则的,每当你要删除一条规则的时候,你必须提供足够的你要删除的规则的详细信息,然后和内核中的规则群做比对,只有在精确匹配成功,即没有任何二义性的匹配成功后,该规则直接从内核中删除,具体的通信机制(Netlink,ioctl等)不重要,重要的是,iptables有多种删除规则的方式。

iptables规则的删除

1.按照rulenum删除

内核会为每一条保存进内核的iptables规则进行编号索引,该索引在特定的HOOK点/TABLE上是唯一的,因此使用rulenum进行删除不会有任何二义性。

2.精确匹配删除

精确匹配删除比较复杂,你必须给出规则的每一个细节,正如你当初添加该规则时一样,和添加动作唯一不同的是,你要把-A改成-D。这种精确匹配删除的成功依赖的就是用户提供的所有match字段,target字段必须和内核中保存的一模一样,精确到字节级别的匹配。如果哪怕有一个字节不匹配,就会有二义性,删除失败。

3.整表整链删除

当你调用 iptables -t $table -F的时候,该表下面的所有规则就不复存在了,由于规则隶属于表,因此不会有二义性。整链删除含义类似,主要是由于,不管表也好,链也罢,都是具体规则的上级组织,正所谓皮之不存,毛将焉附!
问题之所在
以我实际碰到的问题为例,xt_condition的info结构体如下:
enum {
        CONDITION_NAME_LEN = 31,
};

struct xt_condition_mtinfo {
        char name[CONDITION_NAME_LEN];
        __u8 invert;

        /* Used internally by the kernel */
        void *condvar __attribute__((aligned(8)));
};
注意那个注释!condvar字段只用在内核。如果你用过condition模块,你会知道,它除了一个名称参数之外,不会携带任何参数,也就是说仅仅iptables的命令不会对condvar进行任何设置,默认可能是NULL,然而整个结构体,当然也包括condvar,在添加规则的时候,全都会被注入内核并被内核保存起来,condvar的赋值是在内核中进行的,它表示一个内核结构体的地址。
如果说现在使用精确匹配法则删除一条使用condition的规则,会怎样?内核中的该规则中的xt_condition_mtinfo结构体的condvar字段已经被赋值为一个内核态地址空间的地址,它会被用来和用户态的相同结构体进行字节级的比对,第一个字段name显然是精确匹配的,第二个字段也没有问题,condition_parse会处理得很好,然而第三个condvar字段就不匹配了,用户给出的规则中该字段为0,而内核态中该字段是一个地址!因此就会导致删除失败!

证实问题

这个小节其实是无关紧要的,netfilter的hacker们早就修正了问题,但是那只是在编程层面上,而我需要在业务层面上去阐述问题,因为iptables的每一个match和每一个target,除了内置少数几个之外,都是模块的形式提供,并且出现无法删除问题的不仅仅是condition模块一个,有很多模块都有这个问题,因此我个人认为,这并不算iptables的bug,而只是各个模块的bug。
我在xt_condition.c内核文件侦测了规则到达内核后其xt_condition_mtinfo中condvar的值,我这里的结果是:0XFFFF88000A2D0BC0。那么在删除过程中,按照上述结构体的定义,肯定是在第33字节出现了不匹配,调试下来证实了这一点,内核结构体的第33字节是0xC0,而iptables根据用户敲入命令算出来的对应字节是0x0。好吧,我在condition_parse解析回调函数中为其硬编码:
static int condition_parse(int c, char **argv, int invert, unsigned int *flags,
                           const void *entry, struct xt_entry_match **match)
{
        struct xt_condition_mtinfo *info = (void *)(*match)->data;

        if (c == 'X') {
                if (*flags)
                        xtables_error(PARAMETER_PROBLEM,
                                   "Can't specify multiple conditions");

                if (strlen(optarg) < sizeof(info->name))
                        strcpy(info->name, optarg);
                else
                        xtables_error(PARAMETER_PROBLEM,
                                   "File name too long");

                info->invert = invert;
#ifdef _DEBUG
                memset((char *)info+32, 0xC0, 1);
                memset((char *)info+33, 0x0B, 1);
                memset((char *)info+34, 0x2D, 1);
                memset((char *)info+35, 0x0A, 1);
                memset((char *)info+36, 0x00, 1);
                memset((char *)info+37, 0x88, 1);
                memset((char *)info+38, 0xFF, 1);
                memset((char *)info+39, 0xFF, 1);
#endif
                *flags = 1;
                return true;
        }

        return false;
}
这样在make install之后,就可以成功删除了!

问题的解决

出了什么问题我们已经看出来了,只需要告诉iptables,不要比对结构体的condvar字段就可以了,怎么做呢?iptables给出了接口,即xtables_match结构体的userspacesize字段,原始的condition模块给出的定义是:
.userspacesize  = XT_ALIGN(sizeof(struct xt_condition_mtinfo)),
显然这是不对的,而实际上它应该是:
.userspacesize  = XT_ALIGN(sizeof(struct xt_condition_mtinfo))-sizeof(...)-对齐...
有一种更好的办法,即:
.userspacesize  = offsetof(struct xt_condition_mtinfo, condvar ),
这样iptables就会忽略掉condvar字段的比对。
但是,这就意味着你不能随意布局match/target模块结构体中的字段,你必须将“内核专用”的字段全部放在结构体的最后面,然后用offsetof来取第一个这类字段的偏移,将其赋值给userspacesize,这无疑给编程人员带来了负担,我觉得这倒可以算是一个bug。如果开发人员不懂这个规矩,调试都是很难的。更加智能一点的方式是,在构建结构体的时候,让程序员指名字段的用途,或者干脆分成用户态,内核态两个结构体,用户填充后,iptables框架负责将其合并整合。
和其它bug不同的是,这个bug是大量存在的,并且不能一次性解决。如果你在http://bugzilla.netfilter.org上搜索相关的bug,会出现大量结果,涉及多个模块,一个模块修正了并不意味着另一个模块会被修正,虽然说两个模块的作者都犯了同样一个错误。比如,就针对condition模块而言,我在xtables-addons-1.46中第一次遇到它,发现了它的bug,后来下载了xtables-addons-2.2版本,该bug依然健在!

你可能感兴趣的:(大量存在于iptables模块中的BUG)