linux内核协议栈 netfilter 之 ip 层的table、rule、match、target结构分析

本文深入分析了Linux内核协议栈中ip层的table、rule、match和target的数据结构,包括struct xt_table、struct ipt_entry及其子结构、struct xt_match和struct xt_target等,探讨了它们在iptables中的作用和组织形式,以及如何在内核中注册和使用这些结构。
摘要由CSDN通过智能技术生成

在使用iptables过程中,经常会提到table、rule、match和target,这些在内核都有对应的数据结构(rule并没有对应结构体),在理解内核的逻辑代码之前,非常有必要先熟悉这些数据结构,以及内核到底是如何组织它们的,这里顺便分析ip层对于rule、match、target 的组织形式。

目录

1 struct xt_table

1.1 struct xt_table_info

2 规则rule struct ipt_entry

2.1 标准匹配 struct ipt_ip

3 规则匹配 struct ipt_match_entry / xt_entry_match

3.1 struct xt_match

4 规则执行 struct ipt_entry_target / xt_entry_target

4.1 struct xt_target

4.2 实际使用 struct ipt_standard_target

4.2.1 关于标准 target 的 verdict


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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值