LibPcap丟包問題

http://yuzhu428.spaces.live.com/blog/cns!C75B8C7D675DDD53!207.entry

這段時間查看了下 LibPcap 丟包率高的問題。網上也有不少朋友提及,但自己總懷疑自己的問題與他人不一樣,所以鑽進去看了看。

環境描述: Snapgear-3.5.0 / kernel: linux-2.6.x / uClibc / Module: XSCALE/Intel IXP400 / LibPcap-0.9.2 / Snort-2.6.1.1

測試過程:先將板子設置成透明網橋模式,再讓 Snort 工作在日誌記錄模式下( snort –A none -N ),然後由 eth1(PC1)->eth2(PC2) Chariot TCP/High_Performance ,此時平均速度約爲 93Mbps ,最後跑完整個腳本中斷 Snort ,顯示 Dropped: 86% 。丟包率如此駭人,於是我不得不踏上調查征程。

進入snapgear/user/snort/src ,打開until.c 找到Dropped 出處DropStats() ,發現“Snort received ”和“Dropped ”均通過pcap_stats() 得來,因此我覺得事情有些不妙了。

上網查找資料,有不少敘述關於LibPcap 丟包問題的文章,其中《Improving Passive Packet Capture: Beyond Device Polling 》(可在 http://luca.ntop.org/ 中找到)這篇文章敘述得很清楚。但各位先行者所講的就是我碰到的問題嗎?不行,我得看看。

    接着我註釋掉了snapgear/user/snort/src/snort.c/OpenPcap() 中的pcap_setfilter() ,再次測試,結果一樣。於是我再讓snapgear/user/snort/src/snort.c/PcapProcessPacket() 直接return ,再測試,結果並無改觀。我失望了,難道非得讓我去看LibPcap 嗎?沒辦法,看就看吧。

    進入snapgear/lib/libpcap/ 一路查找,終於發現pcap_stats() 鏈着下面pcap-linux.c 中的pcap_stats_linux() ,閱讀了下面一大段註釋,再debugging 確定,天呀,難道要我去看kernel 嗎?“投之亡地而後存,陷之死地然後生”,我已經走上這條路了。

    沒有多想,按註釋直接全文通緝“tp_drops ”,在snapgear/linux-2.6.x/net/packet/af_packet.c packet_rcv() 中抓住了它。懷疑問題出在:

if (atomic_read(&sk->sk_rmem_alloc) + skb->truesize >=

        (Unsigned)sk->sk_rcvbuf)

        goto drop_n_acct;

debugging 證明了懷疑的正確性,並發現sk_rmem_alloc 會突然降爲零。那麼爲什麼會出現sk_rmem_alloc 不夠用呢?爲此,我不得不弄清楚正常情況下sk_rmem_alloc 是怎麼被釋放的。atomic_read() 該死的原子操作,我還不得不感謝它,因爲在查看它的時候發現了它的兄弟atomic_sub() 並最終找到了sock_rfree() 大人,debugging 證明sk_rmem_alloc 確實是由這位大人釋放的。 那什麼時候這位大人才會露面呢?我真的對Linux 認識太少了,慚愧呀!

    正因爲見識少,所以才容易才發現許多驚奇:天呀,原來這麼多內聯函數都被定義在了頭文件中呀。sock_rfree() 便是通過snapgear/linux-2.6.x/include/net/sock.h 中的static inline void skb_set_owner_r(struct sk_buff *skb, struct sock *sk) 掛在了skb->destructor 上。通過最笨拙的辦法,繼續查找destructor ,終於確定了__kfree_skb() 並踩到了更淺的支點kfree_skb() ,事實證明,愚蠢的人自作聰明的後果往往令人慘不忍睹——可愛的kfree_skb() 漫山遍野。我該怎麼辦呀?甚至有點後悔自己潛水太深了。冷靜冷靜,再找新的突破口吧。

    乾脆由pcap_open_live() 出發,看看這個handle 怎麼得來,socket 如何被創建的。碰到了socket() ,於是我再次衝進kernel ,可是找來找去都沒socket() 的原型,我再次迷惑——坦白,此前我根本不知道系統調用這檔子事。查找資料,又是他——九賤,真真感謝這位大哥,在此推薦下他的論壇 http://www.skynet.org.cn/ 。在他的“Linux 內核探索”版塊中有關於socket() 的介紹。snapgear/linux-2.6.x/net/socket.c 中的sys_socketcall() 是與socket 有關的所有系統調用的入口,這個文件中定義了許多socket 系統調用,我也是在這裏找到了sys_socket() 並確認LibPcap 中創建socket 便是通過這個函數實現的。當我尋訪到__sock_create() 時,又發現此處煙波浩淼,真的是傷心透了。一時半會是看不明白的了,扭頭。

    既然pcap_open_live() 巷子太深,那麼我再從pcap_dispatch() 突破。追蹤到snapgear/lib/libpcap/pcap-linux.c 中的pcap_read_packet() ,發現在callback() 調用用戶程序前是通過recvfrom() 取得包的。鬱悶,又找不到原型,又是系統調用。再次感謝九賤,還有《UDP Socket Creation 》的作者,正是看了他們的文章,sk->sk_prot->recvmsg 才被鎖定。遍地找尋了recvmsg ,再根據LibPcap 創建Socket 時選用的類型SOCK_RAWsnapgear/linux-2.6.x/net/ipv4/raw.c 中的raw_recvmsg() 被相中了,因爲它的老家struct proto raw_prot[] 所在的老窩snapgear/linux-2.6.x/net/ipv4/af_inet.cstatic struct inet_protosw inetsw_array[].ops 所指向的inet_dgram_ops.recvmsg 正好等於sock_common_recvmsg 。歡呼——高興得太早了,debugging 確認時令我失望了,snapgear/linux-2.6.x/net/socket.c sys_recvfrom() 調用sock_recvmsg() 調用__sock_recvmsg() 時,sock->ops->recvmsg 更多時候並不等於sock_common_recvmsg ,一團迷霧驟然升起——天呀!

    我深切地觀望着packet_rcv() 。我找不到更好的突破口了,就拿recvmsg 當救命稻草了,再次搜尋recvmsg ,終於,終於在snapgear/linux-2.6.x/net/packet/af_packet.c 中發現了.recvmsg=packet_recvmsgDebugging ,打印函數地址,確認!更喜人的是在packet_recvmsg() 中發現了最終出口skb_free_datagram(),snapgear/linux-2.6.x/net/core/datagram.c 中的它顯示它直接返回kfree_skb()Debugging 確認!

    至此,LibPcap 捕獲數據包的出入口已經找到了,之前贅述,無非是展現本人在尋找這兩扇大門時的經過,以及犯下的愚蠢錯誤,旨在告誡與我一樣還不瞭解Linux 的朋友不要重蹈我的覆轍,也希望廣大高手能夠不吝賜教。

   總結:LibPcap 通過pcap_open_live() 系統調用socket() 創建一個socket. 而系統調用socket() 則是通過sys_socketcall() 這個入口找到sys_socket()->sock_create()->__sock_create()->rcu_dereference(net_families[family]) 根據協議簇執行createLibPcap 選用的協議簇PF_PACKET 通過af_packet.c 中的packet_init() 調用snapgear/linux-2.6.x/net/socket.c 中的sock_register() 被初始化註冊進net_families[] ,其.create=packet create 。因此LibPcap 創建socket 最終調用了packet_create() ,在packet_create() 中創建了sk 並有sock->ops = &packet_ops; po->prot_hook.func=packet_rcv; static const struct proto_ops packet_ops. r ecvmsg=packet_recvmsg ,這便是用戶程序通過LibPcapsocket 取得數據包的入口。因此用戶程序通過LibPcap 獲取數據包的整個過程可以簡易描述爲:由packet_rcv() 接收來自底層的包(具體什麼位置,我沒有搞明白),並分配出一段buffer ,在sk_receive_queue 資源不足以再容納下一段數據時,直接將數據丟棄kfree_skb() ,並記錄tp_drops (即我們通過pcap_stats() 得到的ps_drop );而用戶程序則是不時調用packet_recvmsg() 從隊列中一次性獲取數據,並最後釋放資源skb_free_datagram()

    其實到這裏,我還未交代主題,那麼導致LibPcap 丟包的原因在哪裏呢?瞭解了LibPcap 捕獲數據包的過程再來查找就沒那麼茫然了,debugging 發現packet_recvmsg() 的執行頻度遠小於packet_rcv() ,所以在packet_rcv() 接收數據並充盈sk_rmem_alloc 後,packet_recvmsg() 並不能及時將其清空,在這個時間差中只能丟包了。那麼爲什麼packet_recvmsg() 執行的頻度不夠呢?這可能是更底層的問題,限於能力,我在此無法給出解釋。

    再談談怎麼解決這個問題。由於底層的原因我不得而知,所以我只能對我所瞭解的做出調整了——加大sk_rmem_alloc ,利用其空間來容納packet_rcv() 的積極動作,但這個做法是以犧牲速度爲代價的。在本人的測試環境中,啓用snort 中提供的所有rule ,將sk_rmem_alloc 擴至10Mecho 10485760 > /proc/sys/net/core/rmem_default && echo 10485760 > /proc/sys/net/core/rmem_max )能保證Dropped: 0.00% ,但此時平均速度降至≈16Mbps

    結 束語:此文是本人對此問題進行查尋的筆記,走了很多彎路,如果有朋友也在關心此問題,那麼不妨以本人爲一反面材料,並希望讀者能對文中謬誤之處提出批評並 指正。既然走了這麼多彎路,當然浪費了大量寶貴時間,十分感謝我們老大在此過程中對我提供的大量幫助和對我所持的極大耐心。這些都是我決定寫下本文的原 因。

發佈了24 篇原創文章 · 獲贊 1 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章