注册helper

nf_conntrack_ftp_init是连接跟踪ftp模块的初始化函数,可以看到其调用了nf_conntrack_helpers_register来注册helper;

 static int __init nf_conntrack_ftp_init(void)
{
int i, ret = ; NF_CT_HELPER_BUILD_BUG_ON(sizeof(struct nf_ct_ftp_master)); ftp_buffer = kmalloc(, GFP_KERNEL);
if (!ftp_buffer)
return -ENOMEM; if (ports_c == )
ports[ports_c++] = FTP_PORT; /* FIXME should be configurable whether IPv4 and IPv6 FTP connections
are tracked or not - YK */
/* 初始化helper */
for (i = ; i < ports_c; i++) {
nf_ct_helper_init(&ftp[ * i], AF_INET, IPPROTO_TCP, "ftp",
FTP_PORT, ports[i], ports[i], &ftp_exp_policy,
, help, nf_ct_ftp_from_nlattr, THIS_MODULE);
nf_ct_helper_init(&ftp[ * i + ], AF_INET6, IPPROTO_TCP, "ftp",
FTP_PORT, ports[i], ports[i], &ftp_exp_policy,
, help, nf_ct_ftp_from_nlattr, THIS_MODULE);
} /* 注册helper */
ret = nf_conntrack_helpers_register(ftp, ports_c * );
if (ret < ) {
pr_err("failed to register helpers\n");
kfree(ftp_buffer);
return ret;
} return ;
}

模块通过调用nf_conntrack_helper_register函数将helper添加到对应的hash中;函数首先会对是否有相同匹配的helper进行检查,不存在才会存放到hash中;

 int nf_conntrack_helper_register(struct nf_conntrack_helper *me)
{
struct nf_conntrack_tuple_mask mask = { .src.u.all = htons(0xFFFF) };
unsigned int h = helper_hash(&me->tuple);
struct nf_conntrack_helper *cur;
int ret = , i; BUG_ON(me->expect_policy == NULL);
BUG_ON(me->expect_class_max >= NF_CT_MAX_EXPECT_CLASSES);
BUG_ON(strlen(me->name) > NF_CT_HELPER_NAME_LEN - ); /* 允许的最大期望连接超额 */
if (me->expect_policy->max_expected > NF_CT_EXPECT_MAX_CNT)
return -EINVAL; mutex_lock(&nf_ct_helper_mutex);
/* 遍历helper hash,查找是否已存在,条件(名称相同 &&(三层协议为指定||三层协议相同)&&四层协议相同 */
for (i = ; i < nf_ct_helper_hsize; i++) {
hlist_for_each_entry(cur, &nf_ct_helper_hash[i], hnode) {
if (!strcmp(cur->name, me->name) && /* 名称相同 */
(cur->tuple.src.l3num == NFPROTO_UNSPEC || /* 三层协议未指定 */
cur->tuple.src.l3num == me->tuple.src.l3num) && /* 三层协议相同 */
cur->tuple.dst.protonum == me->tuple.dst.protonum) { /* 四层协议相同 */
ret = -EEXIST;
goto out;
}
}
} /* avoid unpredictable behaviour for auto_assign_helper */
/* 不是userspace,遍历hash,进行tuple和掩码的比较,查看是否是已存在 */
if (!(me->flags & NF_CT_HELPER_F_USERSPACE)) {
hlist_for_each_entry(cur, &nf_ct_helper_hash[h], hnode) {
if (nf_ct_tuple_src_mask_cmp(&cur->tuple, &me->tuple,
&mask)) {
ret = -EEXIST;
goto out;
}
}
} /* 将helper加入到helper hash */
refcount_set(&me->refcnt, );
hlist_add_head_rcu(&me->hnode, &nf_ct_helper_hash[h]);
nf_ct_helper_count++;
out:
mutex_unlock(&nf_ct_helper_mutex);
return ret;
}
关联helper到conntrack

在有新数据包进入的时候,如果没有对应连接跟踪,需要调用init_conntrack新建一个连接跟踪,其中会设置连接跟踪的helper;

 static int
resolve_normal_ct(struct net *net, struct nf_conn *tmpl,
struct sk_buff *skb,
unsigned int dataoff,
u_int16_t l3num,
u_int8_t protonum,
struct nf_conntrack_l3proto *l3proto,
struct nf_conntrack_l4proto *l4proto)
{
const struct nf_conntrack_zone *zone;
struct nf_conntrack_tuple tuple;
struct nf_conntrack_tuple_hash *h;
enum ip_conntrack_info ctinfo;
struct nf_conntrack_zone tmp;
struct nf_conn *ct;
u32 hash; /* 将源目的地址端口协议方向等字段设置到tuple */
if (!nf_ct_get_tuple(skb, skb_network_offset(skb),
dataoff, l3num, protonum, net, &tuple, l3proto,
l4proto)) {
pr_debug("Can't get tuple\n");
return ;
} /* look for tuple match */
/* 从hash中查找tuple */
zone = nf_ct_zone_tmpl(tmpl, skb, &tmp);
hash = hash_conntrack_raw(&tuple, net);
h = __nf_conntrack_find_get(net, zone, &tuple, hash); /* 未找到该tuple */
if (!h) {
/* 创建一个节点 */
h = init_conntrack(net, tmpl, &tuple, l3proto, l4proto,
skb, dataoff, hash);
if (!h)
return ;
if (IS_ERR(h))
return PTR_ERR(h);
} /* 获取到nf_conn */
ct = nf_ct_tuplehash_to_ctrack(h); /* It exists; we have (non-exclusive) reference. */
/* 应答方向,已建立连接应答 */
if (NF_CT_DIRECTION(h) == IP_CT_DIR_REPLY) {
ctinfo = IP_CT_ESTABLISHED_REPLY;
}
/* 原始方向 */
else {
/* Once we've had two way comms, always ESTABLISHED. */
/* 已经见过应答了,那么是已连接状态 */
if (test_bit(IPS_SEEN_REPLY_BIT, &ct->status)) {
pr_debug("normal packet for %p\n", ct);
ctinfo = IP_CT_ESTABLISHED;
}
/* 有期望连接标记,则设置关联字段 */
else if (test_bit(IPS_EXPECTED_BIT, &ct->status)) {
pr_debug("related packet for %p\n", ct);
ctinfo = IP_CT_RELATED;
}
/* 其他情况,新连接 */
else {
pr_debug("new packet for %p\n", ct);
ctinfo = IP_CT_NEW;
}
} /* skb关联nf_conn */
nf_ct_set(skb, ct, ctinfo);
return ;
}

init_conntrack函数调用__nf_ct_try_assign_helper来对helper进行赋值;

 static noinline struct nf_conntrack_tuple_hash *
init_conntrack(struct net *net, struct nf_conn *tmpl,
const struct nf_conntrack_tuple *tuple,
struct nf_conntrack_l3proto *l3proto,
struct nf_conntrack_l4proto *l4proto,
struct sk_buff *skb,
unsigned int dataoff, u32 hash)
{
/*...*/ /* 尝试设置helper */
if (!exp)
__nf_ct_try_assign_helper(ct, tmpl, GFP_ATOMIC); /*...*/ return &ct->tuplehash[IP_CT_DIR_ORIGINAL];
}

__nf_ct_try_assign_helper函数完成对helper的设置,其中在helper为空的时候调用nf_ct_lookup_helper查找helper;

 int __nf_ct_try_assign_helper(struct nf_conn *ct, struct nf_conn *tmpl,
gfp_t flags)
{
struct nf_conntrack_helper *helper = NULL;
struct nf_conn_help *help;
struct net *net = nf_ct_net(ct); /* We already got a helper explicitly attached. The function
* nf_conntrack_alter_reply - in case NAT is in use - asks for looking
* the helper up again. Since now the user is in full control of
* making consistent helper configurations, skip this automatic
* re-lookup, otherwise we'll lose the helper.
*/
if (test_bit(IPS_HELPER_BIT, &ct->status))
return ; /* 原关联存在,记录helper为原关联的helper */
if (tmpl != NULL) {
help = nfct_help(tmpl);
if (help != NULL) {
helper = help->helper;
set_bit(IPS_HELPER_BIT, &ct->status);
}
} /* 新连接跟踪的help */
help = nfct_help(ct); /* helper为空 */
if (helper == NULL) {
/* 根据tuple和mask查找helper */
helper = nf_ct_lookup_helper(ct, net); /* 没找到,赋值为NULL */
if (helper == NULL) {
if (help)
RCU_INIT_POINTER(help->helper, NULL);
return ;
}
} /* help为空 */
if (help == NULL) {
/* 为连接跟踪添加help扩展 */
help = nf_ct_helper_ext_add(ct, helper, flags);
if (help == NULL)
return -ENOMEM;
}
/* 扩展不为空 */
else {
/* We only allow helper re-assignment of the same sort since
* we cannot reallocate the helper extension area.
*/
struct nf_conntrack_helper *tmp = rcu_dereference(help->helper); /* 已有的help和新的helper所属的help不是同一个扩展help */
if (tmp && tmp->help != helper->help) {
RCU_INIT_POINTER(help->helper, NULL);
return ;
}
} /* 设置helper */
rcu_assign_pointer(help->helper, helper); return ;
}
 static struct nf_conntrack_helper *
nf_ct_lookup_helper(struct nf_conn *ct, struct net *net)
{
if (!net->ct.sysctl_auto_assign_helper) {
if (net->ct.auto_assign_helper_warned)
return NULL;
if (!__nf_ct_helper_find(&ct->tuplehash[IP_CT_DIR_REPLY].tuple))
return NULL;
pr_info("nf_conntrack: default automatic helper assignment "
"has been turned off for security reasons and CT-based "
" firewall rule not found. Use the iptables CT target "
"to attach helpers instead.\n");
net->ct.auto_assign_helper_warned = ;
return NULL;
} /* 根据tuple查找 */
return __nf_ct_helper_find(&ct->tuplehash[IP_CT_DIR_REPLY].tuple);
}

__nf_ct_helper_find会遍历第一部分讲到的保存已注册helper的hash表,通过tuple和mask来查找对应helper;

 static 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) };
unsigned int h; if (!nf_ct_helper_count)
return NULL; h = helper_hash(tuple); /* 遍历对应hash,根据tuple和mask查找helper */
hlist_for_each_entry_rcu(helper, &nf_ct_helper_hash[h], hnode) {
if (nf_ct_tuple_src_mask_cmp(tuple, &helper->tuple, &mask))
return helper;
}
return NULL;
}

对比过程,在三层地址,四层端口,协议均相同的情况,认为找到helper;

 static inline bool
nf_ct_tuple_src_mask_cmp(const struct nf_conntrack_tuple *t1,
const struct nf_conntrack_tuple *t2,
const struct nf_conntrack_tuple_mask *mask)
{
int count; /* 判断三层地址是否相同 */
for (count = ; count < NF_CT_TUPLE_L3SIZE; count++) {
if ((t1->src.u3.all[count] ^ t2->src.u3.all[count]) &
mask->src.u3.all[count])
return false;
} /* 判断四层端口是否相同 */
if ((t1->src.u.all ^ t2->src.u.all) & mask->src.u.all)
return false; /* 判断协议是否相同 */
if (t1->src.l3num != t2->src.l3num ||
t1->dst.protonum != t2->dst.protonum)
return false; /* 地址+端口+协议都相同,已存在,返回true */
return true;
}
调用helper

在连接跟踪的ipv4_helper钩子函数中,会查找连接跟踪的对应的helper,并执行helper的help函数完成扩展功能;

 static unsigned int ipv4_helper(void *priv,
struct sk_buff *skb,
const struct nf_hook_state *state)
{
struct nf_conn *ct;
enum ip_conntrack_info ctinfo;
const struct nf_conn_help *help;
const struct nf_conntrack_helper *helper; /* This is where we call the helper: as the packet goes out. */
/* 获取skb关联的nf_conn */
ct = nf_ct_get(skb, &ctinfo);
/* 未关联,或者是 已建立连接的关联连接的响应 */
if (!ct || ctinfo == IP_CT_RELATED_REPLY)
return NF_ACCEPT; /* 获取help扩展 */
help = nfct_help(ct); /* 没有扩展 */
if (!help)
return NF_ACCEPT; /* rcu_read_lock()ed by nf_hook_thresh */
/* 获得helper */
helper = rcu_dereference(help->helper);
if (!helper)
return NF_ACCEPT; /* 执行扩展的help函数 */
return helper->help(skb, skb_network_offset(skb) + ip_hdrlen(skb),
ct, ctinfo);
}

Netfilter 之 连接跟踪的helper的更多相关文章

  1. Netfilter之连接跟踪实现机制初步分析

    Netfilter之连接跟踪实现机制初步分析 原文: http://blog.chinaunix.net/uid-22227409-id-2656910.html 什么是连接跟踪 连接跟踪(CONNT ...

  2. Netfilter 之 连接跟踪相关数据结构

    Netfilter通过连接跟踪来记录和跟踪连接的状态,为状态防火墙和NAT提供基础支持: 钩子点与钩子函数 下图为钩子点和钩子函数的关系图(点击图片查看原图),其中ipv4_conntrack_def ...

  3. Netfilter 之 连接跟踪钩子函数分析

    ipv4_conntrack_defrag ipv4_conntrack_defrag对输入包进行检查,如果是分片包,则调用nf_ct_ipv4_gather_frags函数进行重组: static ...

  4. Netfilter 之 连接跟踪初始化

    基础参数初始化 nf_conntrack_init_start函数完成连接跟踪基础参数的初始化,包括了hash,slab,扩展项,GC任务等: int nf_conntrack_init_start( ...

  5. Netfilter&iptables:如何理解连接跟踪机制?

    如何理解Netfilter中的连接跟踪机制? 本篇我打算以一个问句开头,因为在知识探索的道路上只有多问然后充分调动起思考的机器才能让自己走得更远.连接跟踪定义很简单:用来记录和跟踪连接的状态. 问:为 ...

  6. linux内核netfilter连接跟踪的hash算法

    linux内核netfilter连接跟踪的hash算法 linux内核中的netfilter是一款强大的基于状态的防火墙,具有连接跟踪(conntrack)的实现.conntrack是netfilte ...

  7. linux nf_conntrack 连接跟踪机制 3-hook

    conntrack hook函数分析 enum nf_ip_hook_priorities { NF_IP_PRI_FIRST = INT_MIN, NF_IP_PRI_CONNTRACK_DEFRA ...

  8. linux nf_conntrack 连接跟踪机制 2

    连接跟踪初始化 基础参数的初始化:nf_conntrack_standalone_init 会调用nf_conntrack_init_start 完成连接跟踪基础参数的初始化, hash slab 扩 ...

  9. linux nf_conntrack 连接跟踪机制

    PRE_ROUTING和LOCAL_OUT点可以看作是整个netfilter的入口,而POST_ROUTING和LOCAL_IN可以看作是其出口; 报文到本地:PRE_ROUTING----LOCAL ...

随机推荐

  1. SqlServer 附加数据库出错

    方法一 找到要添加数据库的.mdf文件,点击右键,选择属性 在属性页面点击安全,选择Authenticated Users,单击编辑 Authenticated Users权限中选择完全控制,点击确定 ...

  2. webpack4快速上手

    1.在项目根目录cnpm init -y初始化.生成package.json文件 2.在项目里面使用cnpm安装webpack 3.需要在根目录下新建webpack.config.js文件(在其里面配 ...

  3. .NET webapi 的单元测试

    public abstract class MirAPIUnitTestCommon { public abstract string GetBaseAddress(); /// <summar ...

  4. React ~ 小结

    React 小结 state 与 props react 里,只需更新组件的state,然后根据新的state重新渲染用户界面,不需要操作dom. 添加类的构造函数来初始化状态this.state,类 ...

  5. VScode 配置为 LaTeX 编辑器(IDE)

    VScode 配置为 LaTeX IDE 在Windows中,配置VScode作为LaTeX的编辑器(IDE),并使用SumatraPDF预览PDF文件.主要是LaTeX Workshop扩展的设置, ...

  6. docker container 导入和导出

    目录 docker container 导入和导出 1.前言 2.docker container 的导出 3.docker container 的导入 4.镜像和容器 导出和导入的区别 docker ...

  7. Mac下使用Charles抓包https接口

    1 官方网站下载,安装好Charles https://www.charlesproxy.com/download/ 2 安装ssl证书 3 信任证书 4 手机iPhone配置 ,获取证书url 5 ...

  8. (备忘)Window7下安装Python2.6及Django1.4

    1.Python2.6安装 1.1 Python2.6的下载及安装 Python 版本:2.6 下载地址:http://www.python.org/download/releases/2.6.1/  ...

  9. PHP面试题--基础

    1.PHP语言的一大优势是跨平台,什么是跨平台?一.PHP基础: PHP的运行环境最优搭配为Apache+MySQL+PHP,此运行环境可以在不同操作系统(例如windows.Linux等)上配置,不 ...

  10. web开发:css基础

    一.w3c架构分析 二.css三种引入 三.三种引入的优先级 四.基础选择器 五.长度单位与颜色 六.文件样式操作 七.display 一.w3c架构分析 <!DOCTYPE html> ...