linux route cache相关变迁

来源:互联网 发布:p正装照软件 编辑:程序博客网 时间:2024/06/06 02:56

转载:http://www.linuxdiyf.com/linux/26886.html

Linux3.5之前的协议栈实现在IP层是支持路由cache的,这个cache曾受到了诸多的吐槽,比如面临hash抖动,容易被攻击利用等等,于是去掉了路由cache。此后引入一个叫做下一跳cache的机制,这纯粹是为了将路由表和下一跳在逻辑上分开。
 
下一跳cache逻辑可在我写过的《Linux3.5内核以后的路由下一跳缓存》[附1]一文中管中窥豹,这会儿没有太多时间,就不重复解释了,在那篇文章中没有提到的是关于组播的下一跳处理问题,这里直接给出结论:组播的下一跳不会被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的内核上,没有这个问题!并且,我确实在工作上偶遇了这个问题,在一个运维让我蹲点等故障,我确实抓住了这个问题,确实是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]:Linux3.5内核以后的路由下一跳缓存
 
本文永久更新地址:http://www.linuxdiyf.com/linux/26886.html

0 0
原创粉丝点击