route linux 冲突,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),問題就解決了。

好吧,就是這樣,繼續墮落下去。溫州皮鞋,如蛹化蝶,下雨進水不會胖。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值