`
hulianwang2014
  • 浏览: 692144 次
文章分类
社区版块
存档分类
最新评论
  • bcworld: 排版成这样,一点看的欲望都没有了
    jfinal

ip_conntrack对路由结果的cache

 
阅读更多
本文紧接着《我和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的方式来进行事件传递。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics