Linux 3.5之后取消route cache后的一个组播问题和解决

Linux3.5之前的协议栈实现在IP层是支持路由cache的,这个cache曾受到了诸多的吐槽,比如面临hash抖动,容易被攻击利用等等,于是去掉了路由cache。此后引入一个叫做下一跳cache的机制,这纯粹是为了将路由表和下一跳在逻辑上分开。
        下一跳cache逻辑可在我写过的《 Linux3.5内核以后的路由下一跳缓存》一文中管中窥豹,这会儿没有太多时间,就不重复解释了,在那篇文章中没有提到的是关于组播的下一跳处理问题,这里直接给出结论:组播的下一跳不会被cache!
        这意味着什么?这意味着如果你发送的是一个组播数据包,当从FIB中查找到下一跳之后,会每次分配一个dst_entry(也可以看成是一个rtable,dst_entry是一个通用的,而rtable则仅仅针对IPv4),然后当这个组播包发送完毕后,会立即释放这个dst_entry。这一切好像没有什么问题,但是....
        但是,在rt_set_nexthop这个函数中,针对组播要调用:
static void rt_add_uncached_list(struct rtable *rt)
{
    spin_lock_bh(&rt_uncached_lock);
    list_add_tail(&rt->rt_uncached, &rt_uncached_list);
    spin_unlock_bh(&rt_uncached_lock);
}
估计你已经看到问题了。问题就在这个spin_lock!在组播数据发送完成后,dst_entry会被释放:
static void ipv4_dst_destroy(struct dst_entry *dst)
{
    struct rtable *rt = (struct rtable *) dst;

    if (!list_empty(&rt->rt_uncached)) {
        spin_lock_bh(&rt_uncached_lock);
        list_del(&rt->rt_uncached);
        spin_unlock_bh(&rt_uncached_lock);
    }
}
又是这个spin_lock!试想一下,如果用户态运行的一个进程,比如zebra疯了会怎样。如果一个进程频繁发送组播包,比如是OSPF协议实现的有bug,那么大量的组播包会频繁地操作这个rt_uncached_lock自旋锁,除了组播之外,也有别的情形会有dst_entry不会被cache,这同样要操作这个自旋锁。可悲的是,这是个全局的自旋锁。由此,你可以预见,如果你的一个发送组播的进程发疯(比如它的温州皮鞋进了水),你的CPU会飙高,如果其线程分布在所有的CPU上,那么可以认为这是一次你自找的DoS,虽然不是传统意义上的DDoS...
        因此,遇到这个问题的时候,我第一时间想优化它!就像想倒掉皮鞋里的水一样迫不及待!
        这完全是去掉路由cache之后引入的,这不是bug,不会内核panic,但是会拒绝服务!在比如说2.6.32这样的携带路由cache的内核上,没有这个问题!并且,我确实在工作上偶遇了这个问题,在一个运维让我蹲点等故障的半夜12点,我确实抓住了这个问题,确实是zebra疯了,狂发OSPF组播包!....今天终于有了时间,且半夜被蚊子咬醒,又做了一个噩梦,大半夜起来本想看点关于移动4G自组织网络的东西,无奈那通篇的数学公式让我差点又重新睡去,于是写了一篇关于TCP的文章后,准备着手Fix这个关于自旋锁的问题...
        跟以前一样,为了避免做无用功,我先在kernel.org查看比较新的内核实现,找到了rt_add_uncached_list函数:
static void rt_add_uncached_list(struct rtable *rt)
{
    // 自旋锁成了per CPU的,极大减少了开销
    struct uncached_list *ul = raw_cpu_ptr(&rt_uncached_list);

    rt->rt_uncached_list = ul;

    spin_lock_bh(&ul->lock);
    list_add_tail(&rt->rt_uncached, &ul->head);
    spin_unlock_bh(&ul->lock);
}
Oh!爆炸!这正是我想要的!确实社区也意识到了这个问题的所在,已经修正了,于是我注定写完这篇短文后要无所事事一整天了。紧随着这个add,我猜一下destroy,无非就是从dst_entry中取到一个自旋锁,然后去锁住它,进而删除后在解锁。套路,把锁的粒度细化而已,都是套路,内核里面没什么真高大上的东西,都是套路,简单,易懂。确认一下destroy的实现:
static void ipv4_dst_destroy(struct dst_entry *dst)
{
    struct rtable *rt = (struct rtable *) dst;

    if (!list_empty(&rt->rt_uncached)) {
        // 获取附着在rt上的一个局部自旋锁保护的list
        struct uncached_list *ul = rt->rt_uncached_list;

        spin_lock_bh(&ul->lock);
        list_del(&rt->rt_uncached);
        spin_unlock_bh(&ul->lock);
    }
}
问题就是这样解决的,你只要把内核升级到4.3(可能更早,我没有看git log),问题就解决了。
        好吧,就是这样,继续堕落下去。温州皮鞋,如蛹化蝶,下雨进水不会胖。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值