调用HOOK的包筛选函数必须返回特定的值,这些值以宏的形式定义于头文件include/linux/netfilter.h中(Line15),分别为:
- NF_DROP(0):丢弃此数据报,禁止包继续传递,不进入此后的处理流程;
- NF_ACCEPT(1):接收此数据报,允许包继续传递,直至传递到链表最后,而进入okfn函数;
- 以上两个返回值最为常见
- NF_STOLEN(2):数据报被筛选函数截获,禁止包继续传递,但并不释放数据报的资源,这个数据报及其占有的sk_buff仍然有效(e.g. 将分片的数据报一一截获,然后将其装配起来再进行其他处理);
- NF_QUEQUE(3):将数据报加入用户空间队列,使用户空间的程序可以直接进行处理;
- 在nf_hook_slow()以及nf_reinject()函数(位于net/core/netfilter.c,Line449,Line505)中,当由调用nf_iterate()函数(位于net/core/netfilter.c,Line339)而返回的verdict值为NF_QUEUE时(即当前正在执行的这个HOOK筛选函数要求将数据报加入用户空间队列),会调用nf_queue()函数(位于net/core/netfilter.c,Line407)
- nf_queue()函数将这个数据报加入用户空间队列nf_info(位于include/linux/netfilter.h,Line77),并保存其设备信息以备用
- NF_REPEAT(4):再次调用当前这个HOOK的筛选函数,进行重复处理。
4.HOOK的注册和注销
HOOK的注册和注销分别是通过nf_register_hook()函数和nf_unregister_hook()函数(分别位于net/core/netfilter.c,Line60,76)实现的,其参数均为一个nf_hook_ops结构,二者的实现也非常简单。
nf_register_hook()的工作是首先由HOOK的优先级确定在HOOK链表中的位置,然后根据优先级将该HOOK的nf_hook_ops加入链表;
nf_unregister_hook()的工作更加简单,其实就是将该HOOK的nf_hook_ops从链表中删除。
五、IPTables系统
1.表-规则系统
IPTables是基于Netfilter基本架构实现的一个可扩展的数据报高级管理系统,利用table、chain、rule三级来存储数据报的各种规则。系统预定义了三个table:
- filter:数据报过滤表(文件net/ipv4/netfilter/iptable_filter.c)
监听NF_IP_LOCAL_IN、NF_IP_FORWARD和NF_IP_LOCAL_OUT三个HOOK,作用是在所有数据报传递的关键点上对其进行过滤。
- nat:网络地址转换表
监听NF_IP_PRE_ROUTING、NF_IP_POST_ROUTING和NF_IP_LOCAL_OUT三个HOOK,作用是当新连接的第一个数据报经过时,在nat表中决定对其的转换操作;而后面的其它数据报都将根据第一个数据报的结果进行相同的转换处理。
- mangle:数据报处理表(位于net/ipv4/netfilter/iptable_mangle.c)
2.表的实现
表的基本数据结构是ipt_table(位于include/linux/netfilter_ipv4/ip_tables.h,Line413):
struct ipt_table
{
struct list_head list;
char name[IPT_TABLE_MAXNAMELEN];
struct ipt_replace *table; //表初始化时的“种子表”
unsigned int valid_hooks; //表所监听的HOOK
rwlock_t lock;
struct ipt_table_info *private; //表所存储的数据信息
struct module *me; //如果是模块,那么取THIS_MODULE,否则取NULL
};
上文所提到的filter、nat和mangle表分别是这个数据结构的三个实例:packet_filter(位于net/ipv4/netfilter/iptable_filter.c,Line84)、nat_table(位于net/ipv4/netfilter/ip_nat_rule.c,Line104)以及packet_mangle(位于net/ipv4/netfilter/iptable_mangle.c,Line117){
struct list_head list;
char name[IPT_TABLE_MAXNAMELEN];
struct ipt_replace *table; //表初始化时的“种子表”
unsigned int valid_hooks; //表所监听的HOOK
rwlock_t lock;
struct ipt_table_info *private; //表所存储的数据信息
struct module *me; //如果是模块,那么取THIS_MODULE,否则取NULL
};
- ipt_table_info(位于net/ipv4/netfilter/ip_tables.c,Line86)是实际描述规则表的数据结构:
struct ipt_table_info
{
unsigned int size;
unsigned int number; //表项的数目
unsigned int initial_entries; //初始表项数目
unsigned int hook_entry[NF_IP_NUMHOOKS]; //所影响HOOK的规则入口
unsigned int underflow[NF_IP_NUMHOOKS]; //规则表的最大下界
char entries[0] ____cacheline_aligned; //规则表入口,即真正的规则存储结构 //ipt_entry组成块的起始地址,每个CPU对应一个这样的块
};
3. 规则的实现{
unsigned int size;
unsigned int number; //表项的数目
unsigned int initial_entries; //初始表项数目
unsigned int hook_entry[NF_IP_NUMHOOKS]; //所影响HOOK的规则入口
unsigned int underflow[NF_IP_NUMHOOKS]; //规则表的最大下界
char entries[0] ____cacheline_aligned; //规则表入口,即真正的规则存储结构 //ipt_entry组成块的起始地址,每个CPU对应一个这样的块
};
IPTables中的规则表可以在用户空间中使用,但它所采用的数据结构与内核空间中的是一样的,只不过有些成员不会在用户空间中使用。
一个完整的规则由三个数据结构共同实现,分别是:
- 一个ipt_entry结构,存储规则的整体信息;
- 0或多个ipt_entry_match结构,存放各种match,每个结构都可以存放任意的数据,这样也就拥有了良好的可扩展性;
- 1个ipt_entry_target结构,存放规则的target,类似的,每个结构也可以存放任意的数据。
i.存储规则整体的结构ipt_entry,其形式是一个链表(位于include/linux/netfilter_ipv4/ip_tables.h,Line122):
struct ipt_entry
{
struct ipt_ip ip;
unsigned int nfcache;
u_int16_t target_offset;
u_int16_t next_offset;
unsigned int comefrom;
struct ipt_counters counters;
unsigned char elems[0];
};
其成员如下:{
struct ipt_ip ip;
unsigned int nfcache;
u_int16_t target_offset;
u_int16_t next_offset;
unsigned int comefrom;
struct ipt_counters counters;
unsigned char elems[0];
};
- `struct ipt_ip ip;`:这是对其将要进行匹配动作的IP数据报报头的描述,其定义于include/linux/netfilter_ipv4/ip_tables.h,Line122,其成员包括源/目的IP及其掩码,出入端口及其掩码,协议族等信息。
- `unsigned int nfcache;`:HOOK函数返回的cache标识,用以说明经过这个规则后数据报的状态,其可能值有三个,定义于include/linux/netfilter.h,Line23:
#define NFC_ALTERED 0x8000 //已改变
#define NFC_UNKNOWN 0x4000 //不确定
另一个可能值是0,即没有改变。#define NFC_UNKNOWN 0x4000 //不确定
- `u_int16_t target_offset;`:指出了target的数据结构ipt_entry_target的起始位置,即从ipt_entry的起始地址到match存储结束的位置
- `u_int16_t next_offset;`:指出了整条规则的大小,也就是下一条规则的起始地址,即ipt_entry的起始地址到match偏移再到target存储结束的位置
- `unsigned int comefrom;`:所谓的“back pointer”,据引用此变量的代码(主要是net/ipv4/netfilter/ip_tables.c中)来看,它应该是指向数据报所经历的上一个规则地址,由此实现对数据报行为的跟踪
- `struct ipt_counters counters;`:说明了匹配这个规则的数据报的计数以及字节计数(定义于include/linux/netfilter_ipv4/ip_tables.h,Line100)
- `unsigned char elems[0];`:表示扩展的match开始的具体位置(因为它是大小不确定的)
struct ipt_entry_match
{
union {
struct {
u_int16_t match_size;
char name[IPT_FUNCTION_MAXNAMELEN];
} user;
struct {
u_int16_t match_size;
struct ipt_match *match;
} kernel;
u_int16_t match_size; //总长度
} u;
unsigned char data[0];
};
其中描述match大小的`u_int16_t match_size;`,从涉及这个变量的源码看来,在使用的时候需要注意使用一个宏IPT_ALIGN(位于include/linux/netfilter_ipv4/ip_tables.h,Line445)来进行对齐处理,这应该是由于match、target扩展后大小的不确定性决定的。{
union {
struct {
u_int16_t match_size;
char name[IPT_FUNCTION_MAXNAMELEN];
} user;
struct {
u_int16_t match_size;
struct ipt_match *match;
} kernel;
u_int16_t match_size; //总长度
} u;
unsigned char data[0];
};
在结构中,用户空间与内核空间为不同的实现,内核空间中的描述拥有更多的信息。在用户空间中存放的仅仅是match的名称,而在内核空间中存放的则是一个指向ipt_match结构的指针
结构ipt_match位于include/linux/netfilter_ipv4/ip_tables.h,Line342:
struct ipt_match
{
struct list_head list;
const char name[IPT_FUNCTION_MAXNAMELEN];
int (*match)(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const void *matchinfo,
int offset,
const void *hdr,
u_int16_t datalen,
int *hotdrop);
int (*checkentry)(const char *tablename,
const struct ipt_ip *ip,
void *matchinfo,
unsigned int matchinfosize,
unsigned int hook_mask);
void (*destroy)(void *matchinfo, unsigned int matchinfosize);
struct module *me;
};
其中几个重要成员:{
struct list_head list;
const char name[IPT_FUNCTION_MAXNAMELEN];
int (*match)(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const void *matchinfo,
int offset,
const void *hdr,
u_int16_t datalen,
int *hotdrop);
int (*checkentry)(const char *tablename,
const struct ipt_ip *ip,
void *matchinfo,
unsigned int matchinfosize,
unsigned int hook_mask);
void (*destroy)(void *matchinfo, unsigned int matchinfosize);
struct module *me;
};
- `int (*match)(……);`:指向用该match进行匹配时的匹配函数的指针,match相关的核心实现。返回0或者hotdrop置1时丢弃数据报;返回非0表示匹配成功。
- `int (*checkentry)(……);`:当试图插入新的match表项时调用这个指针所指向的函数,对新的match表项进行有效性检查,即检查参数是否合法;如果返回false,规则就不会加入到IPTables中。
- `void (*destroy)(……);`:当试图删除这个表项,即模块释放时,调用这个指针所指向的函数。
那么总之呢,表和规则的实现如图06-11-20所示
从图06-11-20中不难发现,match的定位如下:
- 起始地址为:当前规则(起始)地址+sizeof(struct ipt_entry);
- 结束地址为:当前规则(起始)地址+ipt_entry->target_offset;
- 每一个match的大小为:ipt_entry_match->u.match_size。
- 起始地址为match的结束地址,即:当前规则(起始)地址+ipt_entry-> target_offset;
- 结束地址为下一条规则的起始地址,即:当前规则(起始)地址+ipt_entry-> next_offset;
- 每一个target的大小为:ipt_entry_target->u.target_size。
include/linux/netfilter_ipv4/ip_tables.h中提供了三个“helper functions”,可用于使对于entry、tartget和match的操作变得方便,分别是:
- 函数ipt_get_target():Line274,作用是取得target的起始地址,也就是上面所说的当前规则(起始)地址+ipt_entry-> target_offset;
- 宏IPT_MATCH_ITERATE():Line281,作用是遍历规则的所有match,并执行同一个(参数中)给定的函数。其参数为一个ipt_entry_match结构和一个函数,以及函数需要的参数。当返回值为0时,表示遍历以及函数执行顺利完成;返回非0值时则意味着出现问题已终止。
- 宏IPT_ENTRY_ITERATE():Line300,作用是遍历一个表中的所有规则,并执行同一个给定的函数。其参数为一个ipt_entry结构、整个规则表的大小,以及一个函数和其所需参数。其返回值的意义与宏IPT_MATCH_ITERATE()类似。
- 那么如何保证传入的ipt_entry结构是整个规则表的第一个结构呢?据源码看来,实际调用这个宏的时候传入的第一个参数都是某个ipt_table_info结构的实例所指向的entries成员,这样就保证了对整个规则表的完整遍历。
当一个特定的HOOK被激活时,数据报就开始进入Netfilter/IPTables系统进行遍历,首先检查`struct ipt_ip ip`,然后数据报将依次遍历各个match,也就是`struct ipt_entry_match`,并执行相应的match函数,即ipt_match结构中的*match所指向的函数。当match函数匹配不成功时返回0,或者hotdrop被置为1时,遍历将会停止。
对match的遍历完成后,会开始检查`struct ipt_entry_target`,其中如果是一个标准的target,那么会检查`struct ipt_standard_target`中的verdict,如果verdict值是正的而偏移却指向不正确的位置,那么ipt_entry中的comefrom成员就有了用武之地——数据报返回所经历的上一个规则。对于非标准的target呢,就会调用target()函数,然后根据其返回值进行后面的处理。
4. 规则的扩展
且听下回分解^_^