Linux 3.5之后取消route cache后的一个组播问题和解决
2016-06-18 10:03
369 查看
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这个函数中,针对组播要调用:
因此,遇到这个问题的时候,我第一时间想优化它!就像想倒掉皮鞋里的水一样迫不及待!
这完全是去掉路由cache之后引入的,这不是bug,不会内核panic,但是会拒绝服务!在比如说2.6.32这样的携带路由cache的内核上,没有这个问题!并且,我确实在工作上偶遇了这个问题,在一个运维让我蹲点等故障的半夜12点,我确实抓住了这个问题,确实是zebra疯了,狂发OSPF组播包!....今天终于有了时间,且半夜被蚊子咬醒,又做了一个噩梦,大半夜起来本想看点关于移动4G自组织网络的东西,无奈那通篇的数学公式让我差点又重新睡去,于是写了一篇关于TCP的文章后,准备着手Fix这个关于自旋锁的问题...
跟以前一样,为了避免做无用功,我先在kernel.org查看比较新的内核实现,找到了rt_add_uncached_list函数:
好吧,就是这样,继续堕落下去。温州皮鞋,如蛹化蝶,下雨进水不会胖。
下一跳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),问题就解决了。
好吧,就是这样,继续堕落下去。温州皮鞋,如蛹化蝶,下雨进水不会胖。
相关文章推荐
- 如何使用GParted调整Linux分区(图解,双语)
- 【C语言】实现Linux下的last命令的基本功能
- linux查找
- linux 查看日志命令
- 【C语言】实现Linux下的cp命令的基本功能
- linux yum install
- linux CentOS 6忘记密码的解决办法
- centos添加第三方源
- 每天一个Linux命令(51)ss命令
- Linux基础:Grep查询&AWK查询
- mysql-bin系列日志文件导致linux vps磁盘占满及解决办法
- 82.Linux之VMware10.0.4_x64安装
- linux安全
- How to Fix GNOME License Not Accepted Issue on CentOS 7
- 查看linux系统版本命令
- Hadoop在Linux系统中的安装及基本操作
- Linux触摸屏驱动测试程序
- Linux驱动学习笔记之触摸屏驱动
- centos6 安装最新mysql
- centos上mysql