基于linux2.6.21
上一节分析了ip层hook回调函数的注册以及调用流程,本节我们就开始分析每一个模块的具体实现。 工欲善其事必先利其器,一个功能模块的代码实现与其数据结构的设计有很大的关系,所以我们本节主要是分析table、rule、match、target相关的数据结构,争取本节能把数据结构的定义以及数据结构之间的关系分析明了。
1. table、rule、match、target等相关数据结构分析
在分析table、rule、match、target之前,先把它们之间的联系图贴出来,看了这个图以后,我们基本上就知道xt_table里rule、match、target之间的关联了。
总体框架图
对于一个xt_table表,通过其private指针,指向了xt_table_info结构,而xt_table_info中的entries指针数组的每一个指针成员指向xt_table表在相应cpu中的rule的首地址。
一个表中的所有rule在一个连续的内存块中,让所有rule在一个连续的内存块中,使对rule规则的遍历寻址成为了可能,因为一个规则相对于下一个规则并没有使用链表的形式连接在一起。
而一个完整的rule规则,包括了ipt_entry、ipt_entry_match、ipt_standard_target组成(其实ipt_entry就代表了一个规则,通过ipt_entry->elems即可找到该规则对应的match)。
下面我们一一分析上图提到的数据结构。
1.1 xt_table
该结构体对应于iptables中的表,目前内核注册的table有filter、mangle、nat、raw表,而这些table根据pf值添加到xt_af[pf].tables链表中。而一个xt_table中包含了该表所支持的hook点与该表里已添加的所有rule规则。
- struct xt_table
- {
- /*用于将xt_table连接在一起的链表成员*/
- struct list_head list;
- /*表名*/
- char name[XT_TABLE_MAXNAMELEN];
- /*该表所感兴趣的hook点*/
- unsigned int valid_hooks;
- /*读写锁*/
- rwlock_t lock;
- /*指针,指向xt_table_info,这个指针指向的xt_table_info才是表的重要成员
- 用于存放规则链*/
- void *private;
- /* 判断该表是否属于一个模块 */
- struct module *me;
- /*协议号*/
- int af;
- };
struct xt_table
{
/*用于将xt_table连接在一起的链表成员*/
struct list_head list;
/*表名*/
char name[XT_TABLE_MAXNAMELEN];
/*该表所感兴趣的hook点*/
unsigned int valid_hooks;
/*读写锁*/
rwlock_t lock;
/*指针,指向xt_table_info,这个指针指向的xt_table_info才是表的重要成员
用于存放规则链*/
void *private;
/* 判断该表是否属于一个模块 */
struct module *me;
/*协议号*/
int af;
};
1.2 xt_table_info
上面分析xt_table时,我们只xt_table通过private指针指向了xt_table_info,xt_table_info里面包括了表中含有的规则,是表比较重要的结构体。
- struct xt_table_info
- {
- /*在一个cpu内,该xt_table所包含的所有规则的内存总数(以字节为单位)*/
- unsigned int size;/*规则的个数*/
- unsigned int number;/*初始化时的规则格式*/
- unsigned int initial_entries;
- /*每条规则链相对于第一条规则链的偏移量*/
- unsigned int hook_entry[NF_IP_NUMHOOKS];/*这个具体用在什么地方我还没有搞懂,看别人的解释为每一条规则链范围的最大
- 上限,不过我看初始化时,其与hook_entry在对应规则链上的值是相等的,目前我在代码里发现对underflow的调用,基本上都是将其与hook_entry在对应规则链上的值设置为
- 相同的,还需要深入分析后确认*/
- unsigned int underflow[NF_IP_NUMHOOKS];
- /* ipt_entry tables: one per CPU */
- /*每一个cpu里,ipt_entry指针,指向ipt_entry规则的首地址*/
- char *entries[NR_CPUS];
- };
struct xt_table_info
{
/*在一个cpu内,该xt_table所包含的所有规则的内存总数(以字节为单位)*/
unsigned int size;/*规则的个数*/
unsigned int number;/*初始化时的规则格式*/
unsigned int initial_entries;
/*每条规则链相对于第一条规则链的偏移量*/
unsigned int hook_entry[NF_IP_NUMHOOKS];/*这个具体用在什么地方我还没有搞懂,看别人的解释为每一条规则链范围的最大
上限,不过我看初始化时,其与hook_entry在对应规则链上的值是相等的,目前我在代码里发现对underflow的调用,基本上都是将其与hook_entry在对应规则链上的值设置为
相同的,还需要深入分析后确认*/
unsigned int underflow[NF_IP_NUMHOOKS];
/* ipt_entry tables: one per CPU */
/*每一个cpu里,ipt_entry指针,指向ipt_entry规则的首地址*/
char *entries[NR_CPUS];
};
关于ipt_tale_info与ipt_table、ipt_entry的关系见上面的总体架构图。
1.3 ipt_entry
下面我们分析下ipt_entry
- struct ipt_entry
- {
- struct ipt_ip ip;
- /* Mark with fields that we care about. */
- unsigned int nfcache;
- /* Size of ipt_entry + matches */
- /*该规则中target结构相对于该ipt_entry首地址的偏移量*/
- u_int16_t target_offset;
- /* Size of ipt_entry + matches + target */
- /* 下一个规则相对于该ipt_entry首地址的偏移量*/
- u_int16_t next_offset;
- /* 这个变量的用途有两个:
- 1.判断table表中的规则链是否存在环路
- 2.遍历规则链链时,用于用户自定义链的规则执行完时返回到主链时使用*/
- unsigned int comefrom;
- /* Packet and byte counters. */
- struct xt_counters counters;
- /*由于在设计时需要match结构与ipt_entry的内存是连续的,但是一个ipt_entry包含的match个数又是可变的,所以定义了一个可变长度数组elems,主要是为了实现动态的申请match内存空间*/
- unsigned char elems[0];
- };
struct ipt_entry
{
struct ipt_ip ip;
/* Mark with fields that we care about. */
unsigned int nfcache;
/* Size of ipt_entry + matches */
/*该规则中target结构相对于该ipt_entry首地址的偏移量*/
u_int16_t target_offset;
/* Size of ipt_entry + matches + target */
/* 下一个规则相对于该ipt_entry首地址的偏移量*/
u_int16_t next_offset;
/* 这个变量的用途有两个:
1.判断table表中的规则链是否存在环路
2.遍历规则链链时,用于用户自定义链的规则执行完时返回到主链时使用*/
unsigned int comefrom;
/* Packet and byte counters. */
struct xt_counters counters;
/*由于在设计时需要match结构与ipt_entry的内存是连续的,但是一个ipt_entry包含的match个数又是可变的,所以定义了一个可变长度数组elems,主要是为了实现动态的申请match内存空间*/
unsigned char elems[0];
};
1.3.1 ipt_ip
对应的ipt_ip定义如下,其主要用于标准匹配
/*
标准匹配时的匹配条件
*/
- struct ipt_ip {
- /* 源、目的ip地址 */
- struct in_addr src, dst;
- /* 源、目的ip地址的掩码*/
- 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*/
- u_int8_t flags;
- /*是否是反转匹配*/
- /* Inverse flags */
- u_int8_t invflags;
- };
struct ipt_ip {
/* 源、目的ip地址 */
struct in_addr src, dst;
/* 源、目的ip地址的掩码*/
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*/
u_int8_t flags;
/*是否是反转匹配*/
/* Inverse flags */
u_int8_t invflags;
};
一个ipt_entry的整体结构如上图。
1.4 ipt_entry_match
我们使用到ipt_entry_match时,就说明这是一个扩展match,对于一个标准match是通过调用函数ip_packet_match,对ipt_entry->ip进行判断来实现的,只有扩展match才会使用该结构体在ipt_entry中添加一个ipt_entry_match变量。
match结构体,包括用户态与内核态联合体,通过这个联合体我们发现
只有match_size是共用的。当用户态需要添加新的规则时,对于新规则的
match,用户态只在ipt_entry_match.u.user.name中设置match的名称,而内核在添加规则时
就会根据ipt_entry_match.u.user.name在链表xt[af].match中查找符合要求的ipt_entry_match,当查找
到时就会对ipt_entry_match.u.kernel.match 进行赋值
- struct ipt_entry_match
- {
- union {
- struct {
- /*该match所占用的内存大小(以字节为单位)*/
- u_int16_t match_size;
- /*该match的名称*/
- char name[IPT_FUNCTION_MAXNAMELEN-1];
- /*该match的版本,
- 通过match的名称与版本信息可以唯一确定一个match。
- */
- u_int8_t revision;
- } user;
- struct {
- /*该match所占用的内存大小(以字节为单位)*/
- u_int16_t match_size;
- /*指向ipt_match结构,对于*/
- struct ipt_match *match;
- } kernel;
- /* Total length */
- u_int16_t match_size;
- } u;
- /*可变长度数组,与下一个match或者target关联*/
- unsigned char data[0];
- };
struct ipt_entry_match
{
union {
struct {
/*该match所占用的内存大小(以字节为单位)*/
u_int16_t match_size;
/*该match的名称*/
char name[IPT_FUNCTION_MAXNAMELEN-1];
/*该match的版本,
通过match的名称与版本信息可以唯一确定一个match。
*/
u_int8_t revision;
} user;
struct {
/*该match所占用的内存大小(以字节为单位)*/
u_int16_t match_size;
/*指向ipt_match结构,对于*/
struct ipt_match *match;
} kernel;
/* Total length */
u_int16_t match_size;
} u;
/*可变长度数组,与下一个match或者target关联*/
unsigned char data[0];
};
ipt_entry_match相关的结构体图如下:
上图是一个ipt_entry_match与ipt_match以及一个ipt_match与xt_af[2].match之间的关联,当我们定义了一个xt_match(xt_match与ipt_match是同一个结构)时,需要将其插入到相应的xt_af[pf].match链表中。而当我们为一个rule规则添加一个扩展match时,根据ipt_entry_match.u.user.name查找xt_af[pf].match链表,当查找到相应的xt_match时,就将其地址赋值给ipt_entry_match.u.kernel.match(将该图与总体框架图搭配使用,就基本明了ipt_table、ipt_table_info、ipt_entry、ipt_entry_match、xt_match、xt_af[pf].match这几个数据结构之间的关系)。
1.4.1 xt_match
而xt_match的定义如下:
- struct xt_match
- {
- struct list_head list; //链表,使该match添加到match链表中
- const char name[XT_FUNCTION_MAXNAMELEN-1];//match名称
- u_int8_t revision;
- /* Return true or false: return FALSE and set *hotdrop = 1 to
- force immediate packet drop. */
- /* Arguments changed since 2.6.9, as this must now handle
- non-linear skb, using skb_header_pointer and
- skb_ip_make_writable. */
- /*
- match处理函数
- */
- int (*match)(const struct sk_buff *skb,
- const struct net_device *in,
- const struct net_device *out,
- const void *matchinfo,
- int offset,
- unsigned int protoff,
- int *hotdrop);
- /* Called when user tries to insert an entry of this type. */
- /* Should return true or false. */
- /*合法性检查函数,在创建一个ipt_entry时,在为其match指针赋值后,即会调用该函数
- 进行合法性检查,当检查失败后,即会返回失败,且说明ipt_table创建失败*/
- int (*checkentry)(const char *tablename,
- const void *ip,
- void *matchinfo,
- unsigned int matchinfosize,
- unsigned int hook_mask);
- /* Called when entry of this type deleted. */
- /*销毁函数*/
- void (*destroy)(void *matchinfo, unsigned int matchinfosize);
- /* Set this to THIS_MODULE if you are a module, otherwise NULL */
- struct module *me;
- };
struct xt_match
{
struct list_head list; //链表,使该match添加到match链表中
const char name[XT_FUNCTION_MAXNAMELEN-1];//match名称
u_int8_t revision;
/* Return true or false: return FALSE and set *hotdrop = 1 to
force immediate packet drop. */
/* Arguments changed since 2.6.9, as this must now handle
non-linear skb, using skb_header_pointer and
skb_ip_make_writable. */
/*
match处理函数
*/
int (*match)(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const void *matchinfo,
int offset,
unsigned int protoff,
int *hotdrop);
/* Called when user tries to insert an entry of this type. */
/* Should return true or false. */
/*合法性检查函数,在创建一个ipt_entry时,在为其match指针赋值后,即会调用该函数
进行合法性检查,当检查失败后,即会返回失败,且说明ipt_table创建失败*/
int (*checkentry)(const char *tablename,
const void *ip,
void *matchinfo,
unsigned int matchinfosize,
unsigned int hook_mask);
/* Called when entry of this type deleted. */
/*销毁函数*/
void (*destroy)(void *matchinfo, unsigned int matchinfosize);
/* Set this to THIS_MODULE if you are a module, otherwise NULL */
struct module *me;
};
如果我们想要添加一个扩展match,就要初始化一个xt_match结构,并且将插入到xt_af[pf].match链表
1.5 ipt_standard_target
该结构体主要是对ipt_entry_target的封装,且增加了变量verdict。
这个verdict起到了很大的作用。
对于target,我们需要知道target有两大类,标准target与扩展target
a)对于标准target,其ipt_entry_target.u.kernel.target为NULL,其只需返回NF_ACCEPT/NF_DROP等操作。
b)对于扩展target,需要调用ipt_entry_target.u.kernel.target,执行扩展target操作,并根据返回值决定是否允许数据通行。
即扩展匹配是根据ipt_entry_target.u.kernel.target决定数据包下一步的执行操作,那标准匹配是根据什么决定数据包下一步的操作的呢?
答案就是verdict,当verdict为小于0时,则-verdict则为数据包下一步的执行操作;当verdict大于0时,则说明该target需要跳转到一个用户自定义链的链首地址,其值为用户自定义链相对于表的第一条链规则的偏移量。
基于以上,我们知道verdict的存在即指明了标准target的动作,又为用户链的存在并生效提供了可能。
- struct ipt_standard_target
- {
- struct ipt_entry_target target;
- int verdict;
- };
struct ipt_standard_target
{
struct ipt_entry_target target;
int verdict;
};
1.6 ipt_entry_target
虽然与ipt_entry_match定义相同,但是ipt_entry_target既可以表示一个标准target,也可以表示一个扩展target。当是一个标准的target时,其ipt_entry_target.u.kernel.target为NULL。
下面我们分析一下这个结构体:
/*
target结构体,包括用户态与内核态联合体,通过这个联合体我们发现
只有target_size是共用的。当用户态需要添加新的规则时,对于新规则的
target,用户态只在ipt_entry_target.u.user.name中设置target的名称,而内核在添加规则时
就会根据ipt_entry_target.u.user.name在链表xt[af].target中查找符合要求的ipt_standard_target,当查找
到时就会对ipt_entry_target.u.kernel.target 进行赋值
*/
- struct ipt_entry_target
- {
- union {
- struct {
- u_int16_t target_size; //target 所占用的内存大小
- /* Used by userspace */
- char name[IPT_FUNCTION_MAXNAMELEN-1];//target 的名字
- /*target的版本号,这个值也有很大的作用,这个值让target的向 上兼容成为了可能。
- 存在以下情况:
- 对于target名称为"ABC ",revision为0的target,我们想对这个 target的扩展target函数做新的架构修改,但是又不想改target的 名称,也不想直接改原target的扩展target函数,这时我们可以重 新添加一个target名称为"ABC",revision为1,且扩展target函数
- 为我们新编写的target。这样既保证了针对原来target "ABC"的 iptables规则能正确执行,又能满足我们新的需求。
- 通过name与revision可以唯一确定一个target
- */
- u_int8_t revision;
- } user;
- struct {
- /*target 所占用的内存大小*/
- u_int16_t target_size;
- /* 扩展target使用,用于指向xt_target */
- struct ipt_target *target;
- } kernel;
- /*target 所占用的内存大小*/
- u_int16_t target_size;
- } u;
- /*可变长数组,与下一个ipt_entry关联*/
- unsigned char data[0];
- };
struct ipt_entry_target
{
union {
struct {
u_int16_t target_size; //target 所占用的内存大小
/* Used by userspace */
char name[IPT_FUNCTION_MAXNAMELEN-1];//target 的名字
/*target的版本号,这个值也有很大的作用,这个值让target的向 上兼容成为了可能。
存在以下情况:
对于target名称为"ABC ",revision为0的target,我们想对这个 target的扩展target函数做新的架构修改,但是又不想改target的 名称,也不想直接改原target的扩展target函数,这时我们可以重 新添加一个target名称为"ABC",revision为1,且扩展target函数
为我们新编写的target。这样既保证了针对原来target "ABC"的 iptables规则能正确执行,又能满足我们新的需求。
通过name与revision可以唯一确定一个target
*/
u_int8_t revision;
} user;
struct {
/*target 所占用的内存大小*/
u_int16_t target_size;
/* 扩展target使用,用于指向xt_target */
struct ipt_target *target;
} kernel;
/*target 所占用的内存大小*/
u_int16_t target_size;
} u;
/*可变长数组,与下一个ipt_entry关联*/
unsigned char data[0];
};
ipt_entry_target相关的结构体图如下:
上图是一个ipt_entry_target与ipt_target以及一个ipt_target与xt_af[2].target之间的关联,当我们定义了一个xt_target(xt_target与ipt_target是同一个结构)时,需要将其插入到相应的xt_af[pf].target链表中。而当我们为一个rule规则添加一个扩展target时,根据ipt_entry_target.u.user.name查找xt_af[pf].target链表,当查找到相应的xt_target时,就将其地址赋值给ipt_entry_target.u.kernel.tagret(将该图与总体框架图搭配使用,就基本明了ipt_table、ipt_table_info、ipt_entry、ipt_entry_target、xt_target、xt_af[pf].target这几个数据结构之间的关系)。
1.6.1 xt_target
xt_target的定义如下:
- struct xt_target
- {
- struct list_head list;//链表,使该match添加到target链表中
- const char name[XT_FUNCTION_MAXNAMELEN-1];//target 名称
- u_int8_t revision;
- /*
- target处理函数,对于SNAT、DNAT即在其target函数里,更新request或者reply方向 ip_conntrack_tuple值
- */
- unsigned int (*target)(struct sk_buff **pskb,
- const struct net_device *in,
- const struct net_device *out,
- unsigned int hooknum,
- const void *targinfo,
- void *userdata);
- /*合法性检查函数,在创建一个ipt_entry时,在为其target指针赋值后,即会调用该函数
- 进行合法性检查,当检查失败后,即会返回失败,且说明ipt_table创建失败*/
- int (*checkentry)(const char *tablename,
- const void *entry,
- void *targinfo,
- unsigned int targinfosize,
- unsigned int hook_mask);
- /*销毁函数,当删除一个规则时调用*/
- void (*destroy)(void *targinfo, unsigned int targinfosize);
- /* 该target是否属于一个模块 */
- struct module *me;
- };
struct xt_target
{
struct list_head list;//链表,使该match添加到target链表中
const char name[XT_FUNCTION_MAXNAMELEN-1];//target 名称
u_int8_t revision;
/*
target处理函数,对于SNAT、DNAT即在其target函数里,更新request或者reply方向 ip_conntrack_tuple值
*/
unsigned int (*target)(struct sk_buff **pskb,
const struct net_device *in,
const struct net_device *out,
unsigned int hooknum,
const void *targinfo,
void *userdata);
/*合法性检查函数,在创建一个ipt_entry时,在为其target指针赋值后,即会调用该函数
进行合法性检查,当检查失败后,即会返回失败,且说明ipt_table创建失败*/
int (*checkentry)(const char *tablename,
const void *entry,
void *targinfo,
unsigned int targinfosize,
unsigned int hook_mask);
/*销毁函数,当删除一个规则时调用*/
void (*destroy)(void *targinfo, unsigned int targinfosize);
/* 该target是否属于一个模块 */
struct module *me;
};
当我们想要添加一个扩展target,就要初始化一个xt_target结构,并且将插入到xt_af[pf].target链表
至此,基本上就把三层netfilter相关的数据结构一一介绍完了,综合以上分析,结合上面的结构关联图,就把结构体ipt_table、ipt_table_info、ipt_entry、ipt_entry_target、xt_target、xt_af[pf].target、xt_af[pf].match、xt_af[pf].tables、xt_match、ipt_ip之间的关联也介绍清楚了。至此,万事俱备,就差分析表的创建、初始化、添加规则、删除规则、遍历表规则、添加match、添加target等具体实现了。只要对数据结构之间的关系清楚了,分析这些功能的实现就顺理成章了。