首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

Linux Netfilter实现机制和扩展技术-Netfilter Frame(2)

Linux Netfilter实现机制和扩展技术-Netfilter Frame(2)

2.5        iptables核心数据结构2.5.1 表
在Linux内核里,iptables用struct ipt_table表示,定义如下(include/linux/netfilter_ipv4/ip_tables.h):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
struct ipt_table
{
    struct list_head list;
      /* 表链 */
    char name[IPT_TABLE_MAXNAMELEN];
      /* 表名,如"filter"、"nat"等,为了满足自动模块加载的设计,
     /* 包含该表的模块应命名为iptable_'name'.o */
    struct ipt_replace *table;
      /* 表模子,初始为initial_table.repl */
    unsigned int valid_hooks;
      /* 位向量,标示本表所影响的HOOK */
    rwlock_t lock;
      /* 读写锁,初始为打开状态 */
    struct ipt_table_info *private;
      /* iptable的数据区,见下 */
    struct module *me;
      /* 是否在模块中定义 */
};
struct ipt_table_info是实际描述表的数据结构(net/ipv4/netfilter/ip_tables.c):
struct ipt_table_info
{
    unsigned int size;
      /* 表大小 */
    unsigned int number;
      /* 表中的规则数 */
    unsigned int initial_entries;
      /* 初始的规则数,用于模块计数 */
    unsigned int hook_entry[NF_IP_NUMHOOKS];
      /* 记录所影响的HOOK的规则入口相对于下面的entries变量的偏移量 */
    unsigned int underflow[NF_IP_NUMHOOKS];
      /* 与hook_entry相对应的规则表上限偏移量,当无规则录入时,
/* 相应的hook_entry和underflow均为0 */
    char entries[0] ____cacheline_aligned;
      /* 规则表入口 */
};




例如内建的filter表初始定义如下(net/ipv4/netfilter/iptable_filter.c):
1
2
3
4
5
6
7
8
9
10
11
static struct ipt_table packet_filter
= { { NULL, NULL },    // 链表
"filter",        // 表名
   &initial_table.repl,    // 初始的表模板
    FILTER_VALID_HOOKS,// 定义为((1 << NF_IP6_LOCAL_IN) |
       (1 << NF_IP6_FORWARD) | (1 << NF_IP6_LOCAL_OUT)),
      即关心INPUT、FORWARD、OUTPUT三点
   RW_LOCK_UNLOCKED,// 锁
NULL,        // 初始的表数据为空
     THIS_MODULE     // 模块标示
};




经过调用ipt_register_table(&packet_filter)后,filter表的private数据区即参照模板填好了。
2.5.2 规则
规则用struct ipt_entry结构表示,包含匹配用的IP头部分、一个Target和0个或多个Match。由于Match数不定,所以一条规则实际的占用空间是可变的。结构定义如下(include/linux/netfilter_ipv4):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct ipt_entry
{
        struct ipt_ip ip;
            /* 所要匹配的报文的IP头信息 */
        unsigned int nfcache;
            /* 位向量,标示本规则关心报文的什么部分,暂未使用 */
        u_int16_t target_offset;
            /* target区的偏移,通常target区位于match区之后,而match区则在ipt_entry的末尾;
            初始化为sizeof(struct ipt_entry),即假定没有match */
        u_int16_t next_offset;
            /* 下一条规则相对于本规则的偏移,也即本规则所用空间的总和,
            初始化为sizeof(struct ipt_entry)+sizeof(struct ipt_target),即没有match */
    unsigned int comefrom;
            /* 位向量,标记调用本规则的HOOK号,可用于检查规则的有效性 */
    struct ipt_counters counters;
            /* 记录该规则处理过的报文数和报文总字节数 */
    unsigned char elems[0];
            /*target或者是match的起始位置 */
}




规则按照所关注的HOOK点,被放置在struct ipt_table::private->entries之后的区域,比邻排列。
2.5.3        规则填写过程
在了解了iptables在核心中的数据结构之后,我们再通过遍历一次用户通过iptables配置程序填写规则的过程,来了解这些数据结构是如何工作的了。
一个最简单的规则可以描述为拒绝所有转发报文,用iptables命令表示就是:
1
iptables -A FORWARD -j DROP;




iptables应用程序将命令行输入转换为程序可读的格式(iptables-standalone.c::main()::do_command(),然后再调用libiptc库提供的iptc_commit()函数向核心提交该操作请求。在libiptc/libiptc.c中定义了iptc_commit()(即TC_COMMIT()),它根据请求设置了一个struct ipt_replace结构,用来描述规则所涉及的表(filter)和HOOK点(FORWARD)等信息,并在其后附接当前这条规则--一个struct ipt_entry结构(实际上也可以是多个规则entry)。组织好这些数据后,iptc_commit()调用setsockopt()系统调用来启动核心处理这一请求:
1
2
3
4
5
6
7
8
setsockopt(
sockfd,        
//通过socket(TC_AF, SOCK_RAW, IPPROTO_RAW)创建的套接字,
//其中TC_AF即AF_INET
   TC_IPPROTO,    //即IPPROTO_IP
   SO_SET_REPLACE,   //即IPT_SO_SET_REPLACE
repl,          //struct ipt_replace结构
sizeof(*repl) + (*handle)->entries.size)    //ipt_replace加上后面的ipt_entry




核心对于setsockopt()的处理是从协议栈中一层层传递上来的,调用过程如下图所示:
图6 规则填写过程nf_sockopts是在iptables进行初始化时通过nf_register_sockopt()函数生成的一个struct nf_sockopt_ops结构,对于ipv4来说,在net/ipv4/netfilter/ip_tables.c中定义了一个ipt_sockopts变量(struct nf_sockopt_ops),其中的set操作指定为do_ipt_set_ctl(),因此,当nf_sockopt()调用对应的set操作时,控制将转入net/ipv4/netfilter/ip_tables.c::do_ipt_set_ctl()中。
对于IPT_SO_SET_REPLACE命令,do_ipt_set_ctl()调用do_replace()来处理,该函数将用户层传入的struct ipt_replace和struct ipt_entry组织到filter(根据struct ipt_replace::name项)表的hook_entry[NF_IP_FORWARD]所指向的区域,如果是添加规则,结果将是filter表的private(struct ipt_table_info)项的hook_entry[NF_IP_FORWARD]和underflow[NF_IP_FORWARD]的差值扩大(用于容纳该规则),private->number加1。
2.5.4        规则应用过程
以上描述了规则注入核内iptables的过程,这些规则都挂接在各自的表的相应HOOK入口处,当报文流经该HOOK时进行匹配,对于与规则匹配成功的报文,调用规则对应的Target来处理。仍以转发的报文为例,假定filter表中添加了如上所述的规则:拒绝所有转发报文。
如1.2节所示,经由本地转发的报文经过路由以后将调用ip_forward()来处理,在ip_forward()返回前,将调用如下代码:
1
2
3
4
5
6
NF_HOOK(PF_INET, NF_IP_FORWARD, skb, skb->dev, dev2, ip_forward_finish)
NF_HOOK是这样一个宏(include/linux/netfilter.h):
#define NF_HOOK(pf, hook, skb, indev, outdev, okfn)         \
(list_empty(&nf_hooks[(pf)][(hook)])                    \
? (okfn)(skb)                              \
: nf_hook_slow((pf), (hook), (skb), (indev), (outdev), (okfn)))




也就是说,如果nf_hooks[PF_INET][NF_IP_FORWARD]所指向的链表为空(即该钩子上没有挂处理函数),则直接调用ip_forward_finish(skb)完成ip_forward()的操作;否则,则调用net/core/netfilter.c::nf_hook_slow()转入Netfilter的处理。
这里引入了一个nf_hooks链表二维数组:
1
struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS];




每一个希望使用Netfilter挂钩的表都需要将表处理函数在nf_hooks数组的相应链表上进行注册。对于filter表来说,在其初始化(net/ipv4/netfilter/iptable_filter.c::init())时,调用了net/core/netfilter.c::nf_register_hook(),将预定义的三个struct nf_hook_ops结构(分别对应INPUT、FORWARD、OUTPUT链)连入链表中:
1
2
3
4
5
6
7
8
9
10
11
12
13
struct nf_hook_ops
{
        struct list_head list;
            //链表
        nf_hookfn *hook;
            //处理函数指针
        int pf;
            //协议号
        int hooknum;
            //HOOK号
        int priority;
            //优先级,在nf_hooks链表中各处理函数按优先级排序
};




对于filter表来说,FORWARD点的hook设置成ipt_hook(),它将直接调用ipt_do_table()。几乎所有处理函数最终都将调用ipt_do_table()来查询表中的规则,以调用对应的target。下图所示即为在FORWARD点上调用nf_hook_slow()的过程:
图7 规则应用流程2.5.5 Netfilter的结构特点
由上可见,nf_hooks链表数组是联系报文处理流程和iptables的纽带,在iptables初始化(各自的init()函数)时,一方面调用nf_register_table()建立规则容器,另一方面还要调用nf_register_hook()将自己的挂钩愿望表达给Netfilter框架。初始化完成之后,用户只需要通过用户级的iptables命令操作规则容器(添加规则、删除规则、修改规则等),而对规则的使用则完全不用操心。如果一个容器内没有规则,或者nf_hooks上没有需要表达的愿望,则报文处理照常进行,丝毫不受Netfilter-iptables的影响;即使报文经过了过滤规则的处理,它也会如同平时一样重新回到报文处理流程上来,因此从宏观上看,就像在行车过程中去了一趟加油站。
Netfilter不仅仅有此高效的设计,同时还具备很大的灵活性,这主要表现在Netfilter-iptables中的很多部分都是可扩充的,包括Table、Match、Target以及Connection Track Protocol Helper,下面一节将介绍这方面的内容。
返回列表