在使用iptables过程中,经常会提到table、rule、match和target,这些在内核都有对应的数据结构(rule并没有对应结构体),在理解内核的逻辑代码之前,非常有必要先熟悉这些数据结构,以及内核到底是如何组织它们的,这里顺便分析ip层对于rule、match、target 的组织形式。
目录
3 规则匹配 struct ipt_match_entry / xt_entry_match
4 规则执行 struct ipt_entry_target / xt_entry_target
4.2 实际使用 struct ipt_standard_target
1 struct xt_table
该结构体对应于iptables中的表,目前内核注册的table有filter、mangle、nat、raw表,而这些table根据pf值添加到 net->xt.tables[table->af] 链表中。而一个xt_table中包含了该表所支持的hook点与该表里已添加的所有rule规则。
struct xt_table
{
//所有的表均注册在网络命名空间的 xt 链表中;
struct list_head list;
//table名字,如"filter", "nat","mangle"
const char name[XT_TABLE_MAXNAMELEN];
//按bit使用,表示该table保存了哪些HOOK点的规则,5个钩子
unsigned int valid_hooks;
//这把锁保护的是table中private指针指向内容
rwlock_t lock;
//实际上指向的是struct xt_table_info对象,其定义见下方
void *private;
/* Set this to THIS_MODULE if you are a module, otherwise NULL */
struct module *me;
//该table属于哪个协议族
int af; /* address/protocol family */
};
struct net {
...
#ifdef CONFIG_NETFILTER
struct netns_xt xt;
#endif
};
struct netns_xt {
struct list_head tables[NPROTO];
};
可见,struct xt_table仅仅是对table的基本描述,并没有保存table中的规则,更多的table内容都在其 private 成员中,该成员指向的结构是 struct xt_talble_info 对象,其定义如下。
1.1 struct xt_table_info
struct xt_table_info
{
//table中所有规则占用的内存大小
unsigned int size;
//table中当前保存的规则数目
unsigned int number;
//初始注册时,table中规则的数量
/* Initial number of entries. Needed for module usage count */
unsigned int initial_entries;
//每个table可以保存多个HOOK点的规则(由table的valid_hook决定),
//从某个HOOK点进入后,检查规则时应该只检查该HOOK点上的规则,因此
//为了给table中的规则按照HOOK点定界,有了下面两个成员:
//hook_entry[]记录了各个HOOK点第一条规则距离entries的偏移;
//underflow[]记录了各个HOOK点最后一条规则距离entries的偏移
unsigned int hook_entry[NF_INET_NUMHOOKS];
unsigned int underflow[NF_INET_NUMHOOKS];
//table中的规则实际上是每个CPU都有一份拷贝,这是为了避免多CPU之间的互斥操作,
//所以实际分配时entries是按照CPU个数分配的。比如有两个CPU,那么就分配2个char指针
//的空间,分别用CPU ID进行索引
char *entries[1];
};
//下面这个辅助宏用于计算struct xt_table_info对象的内存大小,
//注意已经根据CPU个数调整entries的大小
#define XT_TABLE_INFO_SZ (offsetof(struct xt_table_info, entries) \
+ nr_cpu_ids * sizeof(char *))
如上图,rule1~ruleN为table中第一个HOOK点上所有的规则,那么hook_entries[0]就指向rule1(实际上是偏移量而不是指针),underflow[0]指向的就是ruleN,类似的,hook_entries[1]和underflow[1]分别指向ruleN+1和ruleM。更详细的信息见下一篇笔记中对table的分配函数的分析。
2 规则rule struct ipt_entry
struct ipt_entry 代表了一条规则,其定义如下:
/* This structure defines each of the firewall rules. Consists of 3
parts which are 1) general IP header stuff 2) match specific
stuff 3) the target to perform if the rule matches */
struct ipt_entry
{
//规则的基本匹配条件,如IP地址等
struct ipt_ip ip;
/* Mark with fields that we care about. */
unsigned int nfcache;
//这条规则的target距离规则起点的偏移量
u_int16_t target_offset;
//下一条规则距离这条规则起点的偏移量
u_int16_t next_offset;
/* Back pointer */
unsigned int comefrom;
//计数器,每条规则都有计数器,一旦skb匹配这条规则,那么计数器累加,
//计数器有字节数和包数两个统计量
struct xt_counters counters;
//这条规则中的match和target,因为不确定到底有几个match,所以使用零长度数组
unsigned char elems[0];
};
#define ipt_counters xt_counters
struct xt_counters
{
u_int64_t pcnt, bcnt; /* Packet and byte counters */
};
2.1 标准匹配 struct ipt_ip
从定义中可以看出,标准匹配到底包含哪些内容:源IP/目的IP、输入/输出网卡、协议(可以是任意层次的协议编号)。
struct ipt_ip {
//源IP、目的IP以及对应的掩码,skb和该条件匹配时,比较的是和掩码相与后的结果
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;
};
3 规则匹配 struct ipt_match_entry / xt_entry_match
一旦match组成规则时,struct xt_entry_match结构才是真正在规则中保存的match。该结构是由用户空间和内核空间共享的,二者的公共结构是match_size和data字段,match_size表示整个match占用的内存空间。在配置规则时,用户空间指定了name,内核根据name查找系统中已注册的match,然后将match指针指向系统中已注册的struct match对象,从而建立起了规则中的match和match本身两个结构之间的联系。
而当我们为一个rule规则添加一个扩展match时,根据ipt_entry_match.u.user.name查找xt[pf].match链表,当查找到相应的xt_match时,就将其地址赋值给ipt_entry_match.u.kernel.match,注意:这里的xt是全局变量而非struct net中的 xt。
#define XT_FUNCTION_MAXNAMELEN 30
#define ipt_entry_match xt_entry_match
struct xt_entry_match
{
//用户态和内核态使用不同的结构表示match。它们的第一个成员都是match的总大小
union {
struct {
u_int16_t match_size;
/* Used by userspace */
//该match的版本,通过match的名称与版本信息可以唯一确定一个match。
char name[XT_FUNCTION_MAXNAMELEN-1];
u_int8_t revision;
} user;
struct {
u_int16_t match_size;
//指向扩展的match信息(每一个扩展match都是一个xt_match对象)
struct xt_match *match;
} kernel;
/* Total length */
u_int16_t match_size;
} u;
unsigned char data[0];
};
3.1 struct xt_match
在内核中,match都是以模块的形式存在的,内核用struct xt_match来表示一种支持的match。如果我们想要添加一个扩展match,就要初始化一个xt_match结构,并且将插入到xt[pf].match链表。
struct xt_af {
struct mutex mutex;
struct list_head match;//管理所有match
struct list_head target;//管理所有target
#ifdef CONFIG_COMPAT
struct mutex compat_mutex;
struct compat_delta *compat_tab;
unsigned int number; /* number of slots in compat_tab[] */
unsigned int cur; /* number of used slots in compat_tab[] */
#endif
};
static struct xt_af *xt;
struct xt_match
{
struct list_head list;
//每个match都有一个唯一的名字
const char name[XT_FUNCTION_MAXNAMELEN-1];
/* Return true or false: return FALSE and set *hotdrop = 1 to
force immediate packet drop. */
//判断skb是否匹配该match,匹配返回true,不匹配返回false
bool (*match)(const struct sk_buff *skb, const struct net_device *in,
const struct net_device *out, const struct xt_match *match,
const void *matchinfo, int offset, unsigned int protoff, bool *hotdrop);
/* Called when user tries to insert an entry of this type. */
/* Should return true or false. */
//在添加规则时,如果规则中有该match,那么调用该回调检查该match的参数是否正确
bool (*checkentry)(const char *tablename, const void *ip, const struct xt_match *match,
void *matchinfo, unsigned int hook_mask);
/* Called when entry of this type deleted. */
void (*destroy)(const struct xt_match *match, void *matchinfo);
/* Called when userspace align differs from kernel space one */
void (*compat_from_user)(void *dst, void *src);
int (*compat_to_user)(void __user *dst, void *src);
/* Set this to THIS_MODULE if you are a module, otherwise NULL */
struct module *me;
/* Free to use by each match */
unsigned long data;
const char *table;
unsigned int matchsize;
unsigned int compatsize;
unsigned int hooks;
unsigned short proto;
//match所属协议族
unsigned short family;
u_int8_t revision;
};
4 规则执行 struct ipt_entry_target / xt_entry_target
设计思想和struct xt_entry_match基本一致,不同点在于标准target也会有一个struct xt_entry_target结构对应,只不过其target指针为空。对于扩展target,该结构的target指针指向对应的struct xt_target对象。
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 进行赋值
#define ipt_entry_target xt_entry_target
struct xt_entry_target
{
//和ipt_entry_match的定义非常的类似
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;
//target信息,如果target->target()函数指针为NULL,
//那么是一个标准target,否则为扩展target
struct xt_target *target;
} kernel;
/* Total length */
u_int16_t target_size;
} u;
//对于扩展target,该指针指向内容会传给其target()回调,这个指针内容由
//扩展target自由使用,只要内核态和用户态保持一致就可以
unsigned char data[0];
};
4.1 struct xt_target
这是target本身,内核中的target模块需要向内核注册一个struct xt_target对象。
struct xt_target
{
struct list_head list;
const char name[XT_FUNCTION_MAXNAMELEN-1];
/* Returns verdict. Argument order changed since 2.6.9, as this
must now handle non-linear skbs, using skb_copy_bits and
skb_ip_make_writable. */
unsigned int (*target)(struct sk_buff *skb, const struct net_device *in,
const struct net_device *out, unsigned int hooknum,
const struct xt_target *target, const void *targinfo);
/* Called when user tries to insert an entry of this type:
hook_mask is a bitmask of hooks from which it can be called. */
/* Should return true or false. */
bool (*checkentry)(const char *tablename, const void *entry,
const struct xt_target *target, void *targinfo, unsigned int hook_mask);
/* Called when entry of this type deleted. */
void (*destroy)(const struct xt_target *target, void *targinfo);
/* Called when userspace align differs from kernel space one */
void (*compat_from_user)(void *dst, void *src);
int (*compat_to_user)(void __user *dst, void *src);
/* Set this to THIS_MODULE if you are a module, otherwise NULL */
struct module *me;
const char *table;
unsigned int targetsize;
unsigned int compatsize;
unsigned int hooks;
unsigned short proto;
unsigned short family;
u_int8_t revision;
};
4.2 实际使用 struct ipt_standard_target
对于标准target,实际上使用struct ipt_standard_target定义的:
#define ipt_standard_target xt_standard_target
struct xt_standard_target
{
struct xt_entry_target target;
int verdict;
};
该结构的第一个成员是xt_entry_target,用面向对象中多态的思想来看,ipt_standard_target也是一个ipt_entry_target。处理target时,先判定ipt_entry_target的target指针成员为NULL后,说明这是一个标准target,此时再按照ipt_starndard_target的格式解释该target,具体见规则遍历函数ipt_do_table()的实现。
标准匹配有ACCEPT、DROP、RETURN和跳转到自定义链四种情况。特别注意:REJECT属于扩展target。
4.2.1 关于标准 target 的 verdict
对于标准target,其verdict取值具有多样性,而且不同的取值代表的含义不同,其对规则的遍历很重要,下面分情况讨论。
- verdict小于0。这种情况下,表示target是ACCEPT、DROP和RETURN中的一种。
- verdict大于等于0。此时表示跳转到自定义链,verdict的值表示自定义链第一条规则距离表第一条规则的偏移量。
取值 | 含义 |
---|---|
-1 | 对应NF_DROP |
-2 | 对应NF_ACCEPT |
-5 | 对应IPT_RETURN |