在连接跟踪子系统之AF_INET协议族钩子函数
中有看到,在连接跟踪子系统的出口,第一个钩子是ipv4_conntrack_help()(以AF_INET协议族为例),之后才是对新连接的确认钩子。并且help钩子的逻辑就是调用连接跟踪信息块中的help()回调函数。这篇笔记就来看看连接跟踪子系统对helper的管理,相关代码文件为:
代码路径 | 说明 |
---|---|
net/netfilter/nf_conntrack_helper.c | helper框架管理实现文件 |
include/net/netfilter/nf_conntrack_helper.h | helper头文件 |
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);
}