作者:陳鴿
Windows Networking 1: 明明資料包已經到達網卡,為什麼我的伺服器不收包?
前後端收發包的問題,往往排查起來頗為費勁。本系列以網絡問題排查為基礎,總結排查過程和分析結果,一步一步完善對 NDIS (Network Driver Interface Specification) Framework,以及對 Qemu Virtio netkvm 驅動的分析和研究。
問題
Windows Server 通過FTP上傳檔案,在傳輸過程中觸發網絡異常問題導緻傳輸失敗。盡管伺服器網絡經過一段時間後自動恢複,但是每次上傳都很容易重制問題。
基本資訊收集
問題描述粗看起來挺明确的,但是我們還是要理清楚具體細節問題,例如,
1.網絡異常觸發的條件:其他用戶端通過FTP上傳檔案。
2.網絡異常的現象:
- )在這台機器上ping 127.0.0.1,顯示正常。此處說明Windows的TCPIP協定棧工作正常,問題發生在更底層驅動裝置上。如果此處異常,一般我們考慮通過netsh.exe來reset tcpip和winsock。
- )這是一台經典網絡主機,在問題發生的時候,内網網卡工作正常。具體來說就是,其他同内網網段機器ping這台Windows伺服器正常,通過内網網卡ping内網dns正常。
- )問題發生的時候,這台機器嘗試ping外網網卡上配置的網關不通。此處第2和第3步說明網卡驅動本身應該工作正常,問題可能在網卡或者該外網網卡所對應的NDIS Miniport,包括對應的NDIS.sys維護的NDIS_MINIPORT_BLOCK結構和網卡驅動維護的_ADAPTER結構
- )在嘗試ping網關的時候,運作ARP -a 顯示網關MAC和IP映射關系是Incomplete。說明資料包的發送已經嘗試觸發ARP過程,但是無法收到響應。
- )基于第4步,檢查網卡裝置(devmgmt.msc)顯示狀态正常,但是網卡接口(Network Connection -> NIC Properties)顯示發送計數有增加,但接收計數卻沒有增加。這也側面說明ARP請求發送正常,但是沒有收到響應。
- )最後,嘗試禁用啟用網卡,發現問題解決。
3.根據異常現象排查,我們初步定位ARP問題,但通過arp.exe -s 去添加靜态ARP資訊(arp.exe -s gw.ipv4.address ee:ff:ff:ff:ff:ff),發現還是無法ping通網關,說明arp并不是主要的原因。
定位問題
為了更準确的定位問題,我們在Windows主機上以及它的主控端上同時抓包分析。可惜的是,這次的抓包是直接列印在螢幕上,沒有保留具體抓包内容。下次如果遇到類似情況,截圖補上。
口述一下抓包的分析結果,添加靜态ARP之後,VIF口上的抓包我們可以看到ping的ICMP Echo Request封包由vif口發送出去,并收到ICMP Echo Reply封包。
由于之前的種種測試排查,我們認為問題發生在底層驅動,由于問題現場存在,直接從NC上抓取Windows的dump分析。
技巧分享
Windows作業系統自2008 R2開始就內建了抓包能力,功能實作在NDIS.sys上,與Windows的ETW機制協同工作,為我們排查帶來便利。啟用的方式很簡單,運作指令,
netsh trace start capture=yes
複現問題後,運作指令,
netsh trace stop
抓到的日志檔案會被寫在目前使用者的temp目錄下。當然,在運作stop指令後,Windows會把日志檔案的位置列印在cmd.exe視窗中。
使用這個指令抓到的檔案可以使用Microsoft Network Monitor 3.4或者Microsoft Message Analyzer打開。Wireshark暫時無法識别。
另外,額外提一句,Windows的ETW是一個比較好的排查作業系統内部元件行為的工具,Windows提供了一些既有的Scenarios和Providers,可以使用netsh trace show scenarios和netsh trace show providers來檢視。有機會另寫一篇文章作為補充。對于網絡來說,我這邊簡單組合了一下ndis,tcpip,afd,winsock相關的providers,适用一般情況下對系統網絡行為做一些比較深入的研究,指令如下,
netsh trace start provider={2F07E2EE-15DB-40F1-90EF-9D7BA282188A} keywords=0xffffffffffffffff level=0xff provider={E53C6823-7BB8-44BB-90DC-3F86090D48A6} keywords=0xffffffffffffffff level=0xff provider={7D44233D-3055-4B9C-BA64-0D47CA40A232} keywords=0xffffffffffffffff level=0xff provider={50B3E73C-9370-461D-BB9F-26F32D68887D} keywords=0xffffffffffffffff level=0xff provider={43D1A55C-76D6-4F7E-995C-64C711E5CAFE} keywords=0xffffffffffffffff level=0xff maxSize=500MB fileMode=circular persistent=no overwrite=yes report=yes correlation=yes traceFile=c:\NetworkTrace.etl capture=yes packettruncatebytes=128 IPv4.Address=<ipv4.address.for.filtering>
具體指令含義可以參考netsh trace capture help
Memory Dump分析
首先檢視網卡Miniport狀态,并無異常狀态。一般如果網卡異常我們可能會看到例如Pending OID,或者Reset等General資訊,可以嘗試更新網卡驅動。
接下來檢查send path,也就是發送情況。在老版本Windows作業系統,我們可以看mopen上的reference,原理是tcpip.sys driver每在發送的請求的時候都會增加tcpip與miniport的mopen的reference,而在網卡會在封包發送完成後,調用tcpip.sys驅動的回調函數釋放reference。
在Windows Server 2008 R2之後的版本,mopen的reference不再起作用,發送的計數被記錄在tcpip.sys的Provider_Rundown_Protection中,以滿足在不同的CPU上處理發送請求的能力。Rundown的計數針對每個CPU,通過加減法計算有沒有pending NBL未發送。
參考資料:
https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/nf-wdm-exinitializerundownprotection通過其他狀态計數來确認發送接收情況:通過code review,我們找到了Virtio netkvm的統計資訊,進一步确認發送正常,接收異常。
進一步看資料結構, NetReceiveBuffer List裡面是空的,NetNofReceiveBuffers 也是0,應該是網卡驅動發現沒有可用的buffer,導緻就不能繼續收包。這裡面netkvm驅動有很多做法,比如在buffer滿的情況下disbale網卡中斷,也就不會有收報的行為發生。
後續檢查ParaNdis_ProcessRxPath函數和 virtqueue_get_buf 函數,确認ring buffer滿,
根據Virtio netkvm的代碼,NetReceiveBuffersWaiting這個LIST_ENTRY資料結構裡面的buffer内容其實是Windows 作業系統的NDIS 架構驅動負責維護,并調用netkvm驅動注冊好的 ReturnPacketHandler 也就是 netkvm!ParaNdis5_ReturnPacket 來釋放buffer并放回NetReceiveBuffers中。
在這裡,問題集中在為什麼NDIS沒有調用我們的回調函數。Windows對網卡相關的緩存回收主要依靠NET_BUFFER_LIST資料結構的Reference,也就是引用計數。如果buffer被使用,那麼它的reference count就會+1,如果buffer操作完成,對應引用的驅動會調用Dereference來釋放引用。隻有當buffer的reference計數變為0,才會回調釋放buffer函數。
問題的定位是通過枚舉所有未釋放的buffer,列印出網絡包結構,例如,
!list "-t \_LIST\_ENTRY.Flink -e -x \"dt netkvm!IONetDescriptor @$extret; dt ndis!_NDIS_PACKET poi(@$extret+0x40) Private.; dt _MDL poi(poi(@$extret+0x40)+8); db poi(poi(poi(@$extret+0x40)+8)+0x18) L0x50\" 0xfffffadf`37fa26d0"
注:0x0bda = 3034 是FTP Pasv模式的data port。
本案例是Serv-U FTP伺服器不處理收包導緻buffer被用滿,而通過使用Windows build-in的IIS FTP解決。
總結
綜合前面的分析,一般情況下的下一步計劃類似如下,
- Windows 本身NDIS驅動沒有正确處理buffer的釋放,對此,建議是安裝好最新的 ndis.sys 更新檔。
- 其他三方驅動對buffer有不正确的引用,導緻引用計數一直無法為0,得到釋放。建議是解除安裝三方,保持一個幹淨的作業系統。
- 封包沒有被處理。
- Possible Cause 1,資料包收到後需要通過消息通知機制indicate給上層Application,這裡indication慢或者死鎖會導緻問題。
- Possible Cause 2, 應用程式有Critical Section,導緻沒有recv操作發生等等。這中間就涉及了tcpip.sys, afd.sys, winsock, 以及應用程式本身,任何一環出問題都有可能引起網絡問題。
對此,一般的建議是更新驅動tcpip.sys, afd.sys, 和 winsock元件,以及使用其他軟體來替代目前使用的應用程式。