注册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. gdb-example-ncurses

    gdb-example-ncurses http://www.brendangregg.com/blog/2016-08-09/gdb-example-ncurses.html 1. The Prob ...

  2. 编写Dockerfile自定义镜像

    要求 编写一个Dockerfile自定义centos镜像,要求在容器内部可以使用vim和ifconfig命令,并且登入落脚点为/usr/local 编写Dockerfile FROM centos M ...

  3. 使用lodop.js打印控件打印table并分页等

    import {getLodop} from '@/utils/LodopFuncs.js' //打印表格 export default{ // num 打印还是打印预览 conData 对象形式 传 ...

  4. CSS 实现居中 + 清除浮动

    一.水平居中 1.行内元素:text-align:center; 2.块级元素:margin:0 auto; 3.绝对定位和移动:absolute + transform 4.绝对定位和负边距:abs ...

  5. python 修改、读取图片元数据

    图片元数据 图片元数据(metadata)是嵌入到图片文件中的一些标签.比较像文件属性,但是种类繁多.常见的几种标准有: EXIF:通常被数码相机在拍摄照片时自动添加,比如相机型号.镜头.曝光.图片尺 ...

  6. ubuntu安装交叉编译器

    # sudo apt-get install gcc-arm-linux-gnueabi # sudo apt-get install g++-arm-linux-gnueabi 官方下载 https ...

  7. mount -t proc none /proc

    linux initrd里的init脚本中的第一句为: mount -t proc /proc /proc 作用是把proc这个虚拟文件系统挂载到/proc目录.这说明initrd需要用到/proc, ...

  8. NORDIC 协议栈下使用硬件定时器

    在使能蓝牙协议栈后,RTC0.TIMER0被蓝牙协议栈使用,RTC1被软件定时器使用,所以才程序中使用定时器的时候需要避开.

  9. 地沟油jiance

    与地沟油较劲九年(我和我的祖国) 任  飞 2019年06月13日05:00  来源:人民网-人民日报 分享到:     地沟油中含有多种有毒有害物质,但地沟油的检测却是一大难题.单靠眼睛谁也无法判断 ...

  10. 遍历二叉树 - 基于队列的BFS

    之前学过利用递归实现BFS二叉树搜索(http://www.cnblogs.com/webor2006/p/7262773.html),这次学习利用队列(Queue)来实现,关于什么时BFS这里不多说 ...