本文紧接着《
我和ip_conntrack不得不说的一些事》,来看一下如何使用ip_conntrack来cache路由结果。
首先,我们看一下在启用了ip_conntrack的机器上,一个数据包从进入协议栈到发出去,要经历多少查询,然后就知道如果优化掉某些次的查询了。首先需要将一个skb绑定到一个conntrack结构,这就需要一个tuple的查询,此处我们抛开流头的NAT查询以及mangle/filter rule的查询,然后进入ROUTING逻辑,首先要查询一个路由cache(幸运的是,新内核禁掉了cache查询),然后如果没有找到,则查询policy table,这样总共需要3次比较大的查询,如果路由条目很多的话,这3次查询将会非常损耗效率。
既然conntrack为每一个数据包都绑定了一个流,那么就可以将需要查询的东西在查到结果后缓存在这个流结构里面,后续的同一流的包在查询到对应的流结构时,直接取出来使用之,这样的话所有的查询就只归结到conntrack哈希的查询了,并且这种查询可以十分简单的基于硬卡来实现,大大提高了效率。本文的实验仅仅缓存路由,实际上可以缓存的东西很多,正如《我和ip_conntrack不得不说的一些事》http://blog.csdn.net/dog250/article/details/9732185最后所述,很多的策略都可以被conntrack缓存。
那么,在哪个HOOK点来缓存呢?很简单,缓存结果在POST_ROUTING的最后confirm这个地方进行(仅仅针对forward包),而查询缓存结果在PRE_ROUTING的刚刚查询到conntrack结构的地方进行。于是我就修改了ipv4_confirm和ipv4_conntrack_in这两个函数。按照标准做法,不要在nf_conn结构体中增加字段,而是使用其extend机制,遗憾的是,...系统仅仅定义了:
enum nf_ct_ext_id
{
NF_CT_EXT_HELPER,
NF_CT_EXT_NAT,
NF_CT_EXT_ACCT,
NF_CT_EXT_ECACHE,
NF_CT_EXT_NUM,
};
这些个ID,并且写死在了nf_conntrack_extend.h中了,如果修改了就要全部重新编译,本来我想增加一个 NF_CT_EXT_ROUTE的,为了不重新编译,只是借用了NAT这个extend,实现效果即可。需要修改的文件只有一个
$K/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c:
//万恶的我为了偷梁换柱,redifine了nf_conn_nat
struct nf_conn_nat {
struct rtable *rth;
};
static struct nf_ct_ext_type route_extend __read_mostly = {
.len = sizeof(struct nf_conn_nat),
.align = __alignof__(struct nf_conn_nat),
.id = NF_CT_EXT_NAT,
.flags = NF_CT_EXT_F_PREALLOC,
};
//设置conntrack的rtable
static void conn_dst_set(struct nf_conn *ct, struct rtable *dst)
{
struct nf_conn_nat *rt = nf_ct_ext_find(ct, NF_CT_EXT_NAT);
if (rt == NULL) {
rt = nf_ct_ext_add(ct, NF_CT_EXT_NAT, GFP_ATOMIC);
if (rt == NULL) {
return;
}
rt->rth = NULL;
}
#include <net/dst.h>
if (rt->rth == NULL ||
((rt->rth != NULL) && rt->rth->u.dst.output == dst_discard)) {
dst_use(&dst->u.dst, jiffies);
rt->rth = dst;
}
}
static void save_dst(struct sk_buff *skb, struct nf_conn *ct)
{
struct rtable *rth;
rcu_read_lock_bh();
rth = skb_rtable(skb);
if (rth != NULL) {
conn_dst_set(ct, rth);
}
rcu_read_unlock_bh();
}
static unsigned int ipv4_confirm(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;
unsigned int ret;
/* This is where we call the helper: as the packet goes out. */
ct = nf_ct_get(skb, &ctinfo);
//仅仅针对FORWARD包进行路由cache,因此判断HOOKNUM和sock
if (ct && hooknum == NF_INET_POST_ROUTING && skb->sk == NULL &&
ct != &nf_conntrack_untracked) {
save_dst(skb, ct);
}
if (!ct || ctinfo == IP_CT_RELATED + IP_CT_IS_REPLY)
goto out;
....
}
static unsigned int ipv4_conntrack_in(unsigned int hooknum,
struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
unsigned int ret = nf_conntrack_in(dev_net(in), PF_INET, hooknum, skb);
//仅仅在PRE_ROUTING检查过路包
if (ret == NF_ACCEPT && hooknum == NF_INET_PRE_ROUTING) {
enum ip_conntrack_info ctinfo;
struct nf_conn *ct;
struct rtable *rth;
struct nf_conn_nat *rt;
ct = nf_ct_get(skb, &ctinfo);
if (!ct) {
goto out;
}
rcu_read_lock_bh();
rt = nf_ct_ext_find(ct, NF_CT_EXT_NAT);
if (rt == NULL) {
rt = nf_ct_ext_add(ct, NF_CT_EXT_NAT, GFP_ATOMIC);
if (rt == NULL) {
rcu_read_unlock_bh();
goto out;
}
rt->rth = NULL;
}
if ((rth = rt->rth) == NULL) {
rcu_read_unlock_bh();
goto out;
}
dst_use(&rth->u.dst, jiffies);
//以下将conn的路由cache设置进skb,如此一来就不用ROUTING了
skb_dst_set(skb, dst_clone(&rth->u.dst));
rcu_read_unlock_bh();
//注意以下的被注释的代码,实际上放开这些注释的话,所实现的功能和不放开注释
//的效果是完全不同的!以下的注释可以实现HOOK点间的跳转,十分方便和硬卡进行
//接口,你可以在NF_HOOK那一行调用硬卡接口实现直接发送,然后返回NF_STOLEN
// NF_HOOK(PF_INET, NF_INET_FORWARD, skb, skb->dev, rth->u.dst.dev,
// rth->u.dst.input);
// return NF_STOLEN;
}
out:
return ret;;
}
另外,在conntrack被free的时候,一定记得把route extend同时也free掉,否则它们就真的成为游离内存了。
正如3.x的内核将路由cache禁用的理由一样,路由cache本来就应该属于conntrack这个层次,不管是针对五元组的conntrack还是SDN那样更加广义的N元组追踪,它们本质上都是为“转发”这个动作提供一种策略,然后基于这个策略对数据包进行分类,不管是Cisco的CEF还是各种基于Netfilter的硬卡,还是SDN的用户自定义流,都是这种思想的体现,因此3.x的内核并不是说路由cache不好,而是说它应该处在它本应该属于的地方。
但是我的这个第一版修改有以下几个问题:
1.没有notify机制。也就是说如果路由改变了,要更新conntrack里面缓存的路由,或者直接失效它,这个还没有实现;
2.路由cache的timeout问题。因为conntrack中cache的路由同时也被cache到了路由缓存的list中,那么如果删除了呢?
3.没地方show出来当前都cache了哪些路由在conntrack里面
4.其实嘛,ct == &nf_conntrack_untracked也是可以cache路由的啊! 针对路由的conntrack缓存已经实现,针对ACCEPT or DROP的conntrack缓存也在我的上一版修改的基础上和IPMARK结合可以实现,想想看还有什么可以缓存的,在这一版修改后,剩下的就是设计一套硬卡的接口了,将Netfilter的HOOK实现在其中,于是这些硬卡就真正可以STOLEN软实现的数据包转发路径了。实际上,在实现上,如今的Netfilter已经很好,conntrack+IPMARK几乎可以完成所有事情,只是被DROP的流头无法创建conntrack,不过这个已经被我的第一版修改了,现在也没有问题了。在实现的建议上,建议不要增加新的HOOK点,最好用notifier_block的方式来进行事件传递。
分享到:
相关推荐
离线安装包,亲测可用
在基于header信息(IP,端口等)进行过滤的包过滤防火墙发展多年之后,对防火墙的需求也再逐渐丰富,stateless防火墙对探测跟踪以及DoS的防护显得力不从心。当然从历史时间来看,包过滤防火墙是符合当时发展需要,...
官方离线安装包,亲测可用。使用rpm -ivh [rpm完整包名] 进行安装
官方离线安装包,亲测可用
官方离线安装包,亲测可用
官方离线安装包,亲测可用
官方离线安装包,亲测可用
官方离线安装包,亲测可用
官方离线安装包,亲测可用。使用rpm -ivh [rpm完整包名] 进行安装
# 载入相关模块 PATH=/sbin:/bin:/usr/sbin:/usr/bin ...modprobe ip_conntrack_ftp > /dev/null 2>&1 modprobe ip_conntrack_irc > /dev/null 2>&1 modprobe ipt_MASQUERADE > /dev/null 2>&1
ConnView是conntrack表查看器。 这是php脚本-ip_conntrack表的前端。 您可以选择过滤,连接排序。 脚本可识别conntrack表中的常见服务。您可以查看连接列表或每个IP的详细信息列表等。
IPv4 support for nf_conntrack.
This is a module which is used for setting up fake conntracks on packets so that they are not seen by the conntrack/NAT code for linux.
you can redistribute it and or modify it under the terms of the GNU General Public License version 2.
Deleting the dummy variable which kicks off garbage collection.
Print out the per-protocol part of the tuple.
Print out the per-protocol part of the tuple.
Actually only need first 8 bytes.
Print out the per-protocol part of the tuple.