内核调试技巧-逆向寻踪,揭开 LACP 协议流程的神秘面纱
作者:wqiangwang,騰訊 TEG 后臺開發(fā)工程師
本文通過“Kni 映射到內(nèi)核的接口未能發(fā)送 LACP 報文導致 bond 不能聚合”這個問題,來探索內(nèi)核調(diào)試中,對于正在運行的內(nèi)核,通過 systemtap 獲取關(guān)鍵數(shù)據(jù)結(jié)構(gòu)的值的通用方法。
背景
DPDK 支持物理端口 通過 kni 映射到內(nèi)核的虛擬接口作為內(nèi)核的標準 net device,借助內(nèi)核完善的生態(tài)處理相對復雜的網(wǎng)絡(luò)協(xié)議,如 tcp 等,這樣以后,無需在用戶態(tài)實現(xiàn)這些協(xié)議。在 NGW 網(wǎng)關(guān)產(chǎn)品中,同樣的,從物理端口接收的 LACP 報文則通過 kni 注入給內(nèi)核;內(nèi)核向外發(fā)送的 LACP 報文則通過 kni 處理并從物理口出。借助內(nèi)核成熟的 LACP 協(xié)議和生態(tài) 而無需用戶態(tài)自己實現(xiàn) LACP,即可完成 bond 聚合。
但在升級 DPDK-20.11 版本時,出現(xiàn) bond 未能聚合。通過 tcpdump 抓包發(fā)現(xiàn),原來對端交換機已經(jīng)發(fā)送了 LACP 報文,而本端一直未發(fā)送 LACP 報文,所以未能聚合:
分析
1、 既然是本端一直未發(fā)送 LACP 報文, 則內(nèi)核協(xié)議棧沒有調(diào)用 dev_queue_xmit 函數(shù)向外發(fā)送 skb->protocol 為 0x8809 的 skb。剛好手上有一臺相同內(nèi)核版本且 bond 已經(jīng)聚合的設(shè)備,為了縮短 bond 內(nèi)核模塊的代碼學習時間,因此看下這臺設(shè)備當調(diào)用 dev_queue_xmit 函數(shù)發(fā)送 skb->protocol 為 0x8809 的 skb 的 backtrace。編寫 systemtap 腳本如下:
2、 對于本設(shè)備,systemtap probe 函數(shù) bond_3ad_state_machine_handler 發(fā)現(xiàn)其調(diào)用時 ad_lacpdu_send 未調(diào)用。看一下代碼,發(fā)現(xiàn) ad_lacpdu_send 若執(zhí)行,需滿足的條件離不開 port 的訪問,為此必須知道 port 的地址:
這里出現(xiàn)了難點,從上述的 backtrace 調(diào)用棧可知,ad_tx_machine 已經(jīng)被編譯器優(yōu)化,不能通過 systemtap 直接獲取$port 入?yún)⒒蛘?pointer_arg(1)提取入?yún)?#xff0c;因此從匯編入手獲取標識 port 的寄存器的值。先用 systemtap 看下行號和匯編指令的對應關(guān)系:
匯編的幾個[test 和 je/jne]指令剛好和上面的 ad_tx_machine 源碼中 if 的邏輯是一致的。同時上述的 if 條件都滿足的話,則進入 bond_3ad_state_machine_handler+3079 開始的邏輯:
而 ad_lacpdu_send 作為 callee 是沒有被優(yōu)化的,仍以函數(shù)調(diào)用,按照 x64 傳參規(guī)則,%rdi 作為第一個參數(shù)即為 port 的標識。從 mov %r12,%rdi 可看出,獲取%r12 也就獲取了 port。那么問題來了,應該 probe 什么位置,調(diào)用 print_regs 函數(shù)獲取%r12 呢。
3、 從上面的匯編[890-929],[3079,3303]這兩個連續(xù)區(qū)間來看,沒有任何寫指令操作%r12。而匯編[890-929]區(qū)間為 ad_lacpdu_send 能否執(zhí)行的判斷邏輯,所以選擇匯編 890 位置為 probe 位置調(diào)用 print_regs 函數(shù)來獲取寄存器。同時匯編 890 位置對應的源碼位置為 bond_3ad.c:1261:
因此編寫如下 systemtap 腳本:
運行結(jié)果如下:
至此找到 port 的地址了,為了驗證 port 的地址的正確性,此時可以通過 crash 或者 systemtap 繼續(xù)校驗。這里通過 systemtap 提取 port 的 adctor_system 成員的 mac 地址來校驗。
因此編寫如下 systemtap 腳本,驗證 ffff888995109838 是 port:
運行結(jié)果如下,其 mac 地址和系統(tǒng)中記錄的 bond 信息一致:
4、 既然已經(jīng)知道了 port,那看下為什么 port 的成員在上述 if 條件中失敗了,再回顧下這個代碼,顯然需要訪問下 port 的 sm_tx_timer_counter, ntt, sm_vars 三個成員:
因此編寫如下 systemtap 腳本:
運行輸出如下,一目了然,原來 sm_vars & AD_PORT_LACP_ENABLED (0x2)為假。
Tips:從藍色方框,結(jié)合下方參數(shù)可以再次驗證了 port 的地址獲取正確。
5、 為什么 sm_vars & AD_PORT_LACP_ENABLED 為假呢,要知道初始化的時候是置位了 AD_PORT_LACP_ENABLED 標志的
顯然是存在邏輯,復位了 sm_vars 的 AD_PORT_LACP_ENABLED 標志位,搜索下代碼:
原來獲取 port 的 speed 和 duplex 影響了 actor_oper_port_key 的值,而 sm_vars 跟 actor_oper_port_key 直接相關(guān),即源頭是 speed 和 duplex 的問題。從系統(tǒng)記錄的 bond 信息也可以看到 speed 和 duplex 是 unknown 的:
5、 為什么 speed 和 duplex 是 unknown 呢。搜下代碼,推斷 bond 的 slave 口獲取其 speed 和 duplex 失敗了:
編寫腳本驗證查看__ethtool_get_link_ksettings 的返回值,由于編譯器優(yōu)化,systemtap 提示源碼 385 行 不能輸出變量$res 的值:
同樣的方法使用 print_regs,獲取%eax 即__ethtool_get_link_ksettings 返回值:
%eax 是%rax 的低 32 位,因此%eax = 0xffffffa1,這個值是-95,查看 errno 得知,標識的意思是 operation not supported
6、 而 slave 口是前面說的 kni 映射到內(nèi)核的接口,通過__ethtool_get_link_ksettings 的源碼可知,原來 kni 映射到內(nèi)核的接口沒有注冊 ethtool_ops 的 get_link_ksettings/get_settings 方法。
至此該問題的根因分析非常明確了。
解決
1、 kni 映射到內(nèi)核的接口注冊并實現(xiàn) ethtool_ops 的 get_settings 方法
2、 get_settings 設(shè)置 speed 和 duplex
如下:
方法
1、 根據(jù)當前問題點,通過 backtrace 梳理流程,然后找到問題點的核心數(shù)據(jù)結(jié)構(gòu)。
2、 當核心數(shù)據(jù)結(jié)構(gòu)不能直接訪問時,則反匯編,查看可通過哪些寄存器間接獲取。
3、 對標識核心數(shù)據(jù)結(jié)構(gòu)的寄存器,確定正確探測位置,確保探測位置和問題點之間寄存器不會改寫,輸出寄存器的值,systemtap 可調(diào)用 print_regs 方法。
4、 遞歸迭代步驟 1-3。
備注:
內(nèi)核版本:4.14.105-1-tlinux3-0007
Dpdk 版本:20.11
最后
所謂工欲善其事必先利其器,systemtap 是我們分析內(nèi)核,學習內(nèi)核,非常好的工具,這玩意需多寫多練,才熟能生巧。歡迎各位一起切磋,一起玩 systemtap。
總結(jié)
以上是生活随笔為你收集整理的内核调试技巧-逆向寻踪,揭开 LACP 协议流程的神秘面纱的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: strstrsubstr、AfxGetA
- 下一篇: python 制作抽奖箱_用Excel函