连接跟踪子系统之helper

连接跟踪子系统之AF_INET协议族钩子函数
中有看到,在连接跟踪子系统的出口,第一个钩子是ipv4_conntrack_help()(以AF_INET协议族为例),之后才是对新连接的确认钩子。并且help钩子的逻辑就是调用连接跟踪信息块中的help()回调函数。这篇笔记就来看看连接跟踪子系统对helper的管理,相关代码文件为:

代码路径说明
net/netfilter/nf_conntrack_helper.chelper框架管理实现文件
include/net/netfilter/nf_conntrack_helper.hhelper头文件

1. helper定义

协议是否需要实现helper是可选的,如果需要实现,那么协议需要首先实例化struct nf_conntrack_helper对象,然后将其注册到系统中才行。helper定义如下:

struct nf_conntrack_helper
{
	struct hlist_node hnode;	/* Internal use. */
	//每个helper模块可以有一个名字
	const char *name;		/* name of the module */
	struct module *me;		/* pointer to self */
	//一个连接允许同时存在的最大期望连接数
	unsigned int max_expected;
	//属于该helper的期望连接的保活定时器超时时间
	unsigned int timeout;
	/* Tuple of things we will help (compared against server response) */
	//指明helper模块关心什么样的数据包。该字段很关键,它决定了新连接建立时
	//能否匹配到该helper对象
	struct nf_conntrack_tuple tuple;
	/* Function to call when data passes; return verdict, or -1 to invalidate. */
	//help()回调
	int (*help)(struct sk_buff *skb, unsigned int protoff,
		    struct nf_conn *ct, enum ip_conntrack_info conntrackinfo);
	void (*destroy)(struct nf_conn *ct);
	int (*to_nlattr)(struct sk_buff *skb, const struct nf_conn *ct);
};

从helper对象的定义来看,helper和期望连接是紧密相关的。

此外,helper会使用连接跟踪的扩展部分,扩展部分保存的实际上是sturct nf_conn_help,该结构定义如下:

//当前内核版本有如下4个用户使用helper
union nf_conntrack_help {
	/* insert conntrack helper private data (master) here */
	struct nf_ct_ftp_master ct_ftp_info;
	struct nf_ct_pptp_master ct_pptp_info;
	struct nf_ct_h323_master ct_h323_info;
	struct nf_ct_sane_master ct_sane_info;
};
/* nf_conn feature for connections that have a helper */
struct nf_conn_help {
	//指向创建该扩展的helper
	struct nf_conntrack_helper *helper;
	//help私有信息
	union nf_conntrack_help help;
	//一个连接可以用多个期望连接,这些同属一个连接的期望连接被组织成一个链表
	struct hlist_head expectations;
	//expectations链表的长度
	unsigned int expecting;
};

2. helper子模块初始化

helper是连接跟踪子系统必备的一个子模块,它在连接跟踪子系统的初始化过程中被初始化。

static DEFINE_MUTEX(nf_ct_helper_mutex);
static struct hlist_head *nf_ct_helper_hash __read_mostly;
//哈希桶大小
static unsigned int nf_ct_helper_hsize __read_mostly;
//哈希表中已保存helper对象的个数
static unsigned int nf_ct_helper_count __read_mostly;
static int nf_ct_helper_vmalloc;
//对于help类型扩展,连接跟踪信息块的扩展字段中真正保存的的struct nf_conn_help
static struct nf_ct_ext_type helper_extend __read_mostly = {
	.len	= sizeof(struct nf_conn_help),
	.align	= __alignof__(struct nf_conn_help),
	.id	= NF_CT_EXT_HELPER,
};

int nf_conntrack_helper_init(void)
{
	int err;
	//分配一个哈希表用于保存注册的helper模块
	nf_ct_helper_hsize = 1; /* gets rounded up to use one page */
	nf_ct_helper_hash = nf_ct_alloc_hashtable(&nf_ct_helper_hsize, &nf_ct_helper_vmalloc);
	if (!nf_ct_helper_hash)
		return -ENOMEM;
	//helper需要使用连接跟踪信息块的扩展字段,该扩展字段的实际内容由各个使用扩展的模块决定,
	//所以这里需要先注册一个扩展
	err = nf_ct_extend_register(&helper_extend);
	if (err < 0)
		goto err1;
	return 0;
...
}

对于扩展,见笔记连接跟踪子系统之extend,其中有以helper为例介绍的扩展的注册。

注意:helper子模块负责的是管理系统中所有已经注册的helper,为ftp这类用户使用helper提供便利,它本身并不注册任何的helper对象。

3. helper的注册

连接跟踪子系统对于helper提供的是一种框架上的支持,它仅仅是管理系统中已注册的helper对象,并且为helper在Netfilter中发挥作用提供一种执行机制,具体每个helper模块干些什么,框架并不操心。

所以,如果有协议要使用helper,必须先向连接跟踪子系统注册一个struct nf_conntrack_helper对象,通过该对象,说明自己想要处理的数据包(tuple成员)类型、以及匹配到该数据包后需要执行的help()回调。

int nf_conntrack_helper_register(struct nf_conntrack_helper *me)
{
	//根据tuple计算哈希值
	unsigned int h = helper_hash(&me->tuple);

	BUG_ON(me->timeout == 0);
	//将helper模块添加到全局的哈希表中并递增已注册helper模块的个数
	mutex_lock(&nf_ct_helper_mutex);
	hlist_add_head_rcu(&me->hnode, &nf_ct_helper_hash[h]);
	nf_ct_helper_count++;
	mutex_unlock(&nf_ct_helper_mutex);
	return 0;
}
EXPORT_SYMBOL_GPL(nf_conntrack_helper_register);

去注册是相反的操作,只不过它需要做更多的同步与检查工作,因为去注册时,当前可能有连接正在使用该helper模块,所以需要同步这些连接的状态。

4. helper的查找

连接跟踪子系统之核心实现中有看到,连接跟踪子系统在检测到一个新的连接时,会通过调用init_conntrack()为其创建连接跟踪信息块。之前的博客并没有仔细分析其中关于期望连接和helper相关的处理逻辑,这里我们重点来看helper的处理,期望连接的处理见连接跟踪子系统之期望连接

4.1 init_conntrack()

/* Allocate a new conntrack: we return -ENOMEM if classification
   failed due to stress.  Otherwise it really is unclassifiable. */
static struct nf_conntrack_tuple_hash * init_conntrack(const struct nf_conntrack_tuple *tuple,
	struct nf_conntrack_l3proto *l3proto, struct nf_conntrack_l4proto *l4proto,
	struct sk_buff *skb, unsigned int dataoff)
{
	struct nf_conn *ct;
	struct nf_conn_help *help;
	struct nf_conntrack_tuple repl_tuple;
	struct nf_conntrack_expect *exp;
...
	spin_lock_bh(&nf_conntrack_lock);
	//根据skb的tuple搜索期望连接链表,检查该新的连接是否是某个已有连接的期望连接
	exp = nf_ct_find_expectation(tuple);
	if (exp) {
		//新连接时某个连接的期望连接,见笔记"连接跟踪子系统之期望连接"
		...
	} else {
		//没有找到期望连接,检查系统中是否有helper模块想处理这种连接,
		//为什么这里要用Reply方向的tuple查找helper呢?
		struct nf_conntrack_helper *helper;
		//这里为什么是使用repl_tuple来查找helper,不理解!!!
		helper = __nf_ct_helper_find(&repl_tuple);
		if (helper) {
			//找到了处理该连接的helper对象,那么在连接信息块的扩展字段中添加一个help类型的扩展,
			//help类型的扩展的实际类型是struct nf_conn_help,这里返回分配的help对象,然后建
			//立help和helper之间的关系,因为通过连接跟踪信息块能够拿到的实际上是help对象
			help = nf_ct_helper_ext_add(ct, GFP_ATOMIC);
			if (help)
				rcu_assign_pointer(help->helper, helper);
		}
		NF_CT_STAT_INC(new);
	}
...
}

4.2 查找helper:__nf_ct_helper_find()

根据参数指定的tuple,查找全局的helper哈希表nf_ct_helper_hash,返回对应的helper对象。

struct nf_conntrack_helper * __nf_ct_helper_find(const struct nf_conntrack_tuple *tuple)
{
	struct nf_conntrack_helper *helper;
	struct nf_conntrack_tuple_mask mask = { .src.u.all = htons(0xFFFF) };
	struct hlist_node *n;
	unsigned int h;
	//如果当前系统中根本就没有注册的helper,当然也就没有helper对该连接感兴趣了
	if (!nf_ct_helper_count)
		return NULL;
	//根据tuple计算hash值
	h = helper_hash(tuple);
	//遍历哈希表中h对应的冲突链,寻找helper->tuple与入参tuple一致的helper
	hlist_for_each_entry_rcu(helper, n, &nf_ct_helper_hash[h], hnode) {
		//匹配原则就是检查L3协议地址、L3协议号、L4协议地址是否相等
		if (nf_ct_tuple_src_mask_cmp(tuple, &helper->tuple, &mask))
			return helper;
	}
	return NULL;
}
EXPORT_SYMBOL_GPL(__nf_ct_helper_find);

4.3 添加help扩展: nf_ct_helper_ext_add()

struct nf_conn_help *nf_ct_helper_ext_add(struct nf_conn *ct, gfp_t gfp)
{
	struct nf_conn_help *help;
	//调用extend子模块的接口添加一个help类型的扩展,见"连接跟踪子系统之extend"
	help = nf_ct_ext_add(ct, NF_CT_EXT_HELPER, gfp);
	if (help)
		INIT_HLIST_HEAD(&help->expectations);
	else
		pr_debug("failed to add helper extension area");
	return help;
}
EXPORT_SYMBOL_GPL(nf_ct_helper_ext_add);

5. help()回调的调用

以AF_INET协议族的help钩子函数为例,来看看help()回调函数是如何被执行的。help钩子以较低优先级工作在连接跟踪子系统的出口处。

static unsigned int ipv4_conntrack_help(unsigned int hooknum, struct sk_buff *skb,
		const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *))
{
	struct nf_conn *ct;
	enum ip_conntrack_info ctinfo;
	const struct nf_conn_help *help;
	const struct nf_conntrack_helper *helper;

	//在入口处,skb应该已经找到了它的连接跟踪信息块
	ct = nf_ct_get(skb, &ctinfo);
	//没有ct,说明该skb没有被跟踪。或者skb属于一个特殊状态(不理解何时会是这个状态)。
	//这两种情况,不会对该skb执行help()回调
	if (!ct || ctinfo == IP_CT_RELATED + IP_CT_IS_REPLY)
		return NF_ACCEPT;
	//获取skb的help信息,该信息在连接跟踪的入口处就已经指定了(如果有的话)
	help = nfct_help(ct);
	if (!help)
		return NF_ACCEPT;
	/* rcu_read_lock()ed by nf_hook_slow */
	helper = rcu_dereference(help->helper);
	if (!helper)
		return NF_ACCEPT;
	//执行help()回调,回调函数的返回值将作为给Netfilter框架的返回值
	return helper->help(skb, skb_network_offset(skb) + ip_hdrlen(skb), ct, ctinfo);
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值