windows7以上平臺NDIS6框架的NDIS協議驅動開發

                                                                                           by fanxiushu 2019-01-30 轉載或引用請註明原始作者。

提到NDIS協議驅動,可能比較陌生,因爲畢竟用得挺少的。
但是一提到WireShark或ethereal等抓包軟件,大家就不再陌生了。
這些抓包軟件在windows平臺大都使用的是winpcap接口庫,winpcap應該也不陌生。
至少在我大學期間就開始接觸到了這個接口庫。
只是當時對它的實現原理並不熟悉,也就只會簡單調用它提供的接口來抓包玩,即便如此,當時也覺得挺神奇的。
這麼多年過去,隨着對操作系統底層的瞭解,對網絡底層的認識,WINPCAP變得不再那麼神祕。
winpcap在驅動層其實就是NDIS協議驅動來實現網絡數據包截獲的,只是它很早就開發出來了,使用的是NDIS5的框架。
同時支持 WIN2000,winxp,win7,win8,win10等平臺,甚至還能支持很早前的win9X平臺。
目前NDIS5 框架的協議驅動,NDIS5 框架的網卡驅動,支持WIN8, WIN10這些最新的平臺,
也就是WINPCAP在win10同樣工作的很好。
NDIS5框架的中間層驅動,在WIN10上會有些問題。

大概在好幾年前,就已經開發了NDIS的三大驅動,不過採用的全是 NDIS5.1框架。
開發NDIS虛擬網卡驅動來實現虛擬局域網,開發NDIS協議驅動來實現虛擬局域網的橋接,
開發NDIS中間驅動和TDI驅動來實現NAT功能和防火牆。
這在CSDN上,我的很早前的文章和資源下載中,都能看到一些這方面的影子。

現在討論的是基於NDIS6 框架的協議驅動。
隨着win7平臺,微軟對內核網絡通訊的全新架構,已經與以前的系統發生了翻天覆地的變化。
整個網絡內核變得更加複雜,同時功能也更加強大。
WFP替代了WINXP中的TDI,關於WFP的介紹,可以參看我下面鏈接中的文章:
https://blog.csdn.net/fanxiushu/article/details/78221340   ( Windows7以上使用WFP驅動框架實現IP數據包截取(一))
https://blog.csdn.net/fanxiushu/article/details/78347137  (  Windows7以上使用WFP驅動框架實現IP數據包截取(二))
 
 NDIS中用於承載數據包的數據結構不再是 NDIS_BUFFER,NDIS_PACKET,
而是 NET_BUFFER, NET_BUFFER_LIST。
NET_BUFFER_LIST,NET_BUFFER都是單鏈表,多個NET_BUFFER_LIST可以使用數據結構裏邊的Next連接成一個串,
同樣的,NET_BUFFER也可以連成一串,每個NET_BUFFER_LIST可以包含一個或多個NET_BUFFER。
多個NET_BUFFER連成串之後,最後掛載到NET_BUFFER_LIST中的FirstNetBuffer上。
NET_BUFFER裏邊又可以包含一個MDL或多個MDL,MDL其實也是單鏈表,也可以形成MDL鏈。
MDL形成串之後,最後掛載到NET_BUFFER中的MdlChain上。
具體的描述請仔細查閱MSDN文檔或WDK驅動開發包相關的頭文件聲明。

MDL裏邊存儲的就是具體的數據包。
具體來說,NET_BUFFER存儲就是一個完整的網卡數據包,用於在物理網上傳輸用的,比如以太網卡,基本都是不超過1514的數據包。
而每個NET_BUFFER也可能包含多個MDL,這種情況基本是在通訊協議棧比如TCP/IP協議棧對數據包進行分析處理造成的,
比如可能把一個完整數據包拆成 MAC網卡頭,IP頭,TCP頭,用戶數據內容;這些數據分別存儲到不同MDL中,
從而一個NET_BUFFER中出現多個MDL。可以使用NdisGetDataBuffer一次性獲取所有這些數據,
或者對每個MDL調用 MmGetSystemAddressForMdlSafe 獲取數據之後,再把這些數據拼接起來。

再來看看NDIS6協議驅動的開發流程。
這些流程以及NET_BUFFER_LIST等的闡述都在介紹NdisFilter驅動的時候簡單介紹過。詳見下面的連接:
https://blog.csdn.net/fanxiushu/article/details/86516610 (Windows7以上平臺 NdisFilter網卡過濾驅動開發)

在DriverEntry入口函數中,初始化 NDIS_PROTOCOL_DRIVER_CHARACTERISTICS   數據結構。
然後調用 NdisRegisterProtocolDriver註冊我們的協議。
數據結構裏邊有個Name參數,系統會根據Name來識別我們的協議還是別的協議驅動。
重點是裏邊的回調函數接口,其實回調函數並不多,也就八,九個回調函數。
比起同樣是NDIS的網卡驅動來說回調函數挺少的,也挺好理解。至少網卡驅動還會牽涉到硬件相關的比如中斷之類的操作。
比起NdisFilter驅動和NDIS中間驅動來說,就更顯得簡單了。

我們先把回調函數分成以下幾大類。
一,初始化回調函數,就是裏邊的 Bind相關函數。具體是
   ProtocolBindAdapterEx, 綁定到某塊網卡
   ProtocolUnbindAdapterEx,解綁某塊網卡
   ProtocolOpenAdapterCompleteEx,打開某塊網卡,已經完成
   ProtocolCloseAdapterCompleteEx。
關閉某塊網卡,已經完成
 

二,Oid請求完成函數和PNP事件和狀態報告函數。
       ProtocolRequestComplete ,調用NdisOidRequest函數發送OID請求完成,
       ProtocolStatusEx, 指示網卡狀態變化,比如連接狀態的改變等
       ProtocolPnPEventHandler,網卡的PnP事件,比如網卡是否重啓,暫停,電源是否關閉等

三,數據包接收或發送完成回調函數。
      ProtocolReceiveNetBufferLists, 接收到網卡發來的數據包
      ProtocolSendComplete, 調用NdisSendBufferLists函數完成之後被調用。

當使用inf配置文件安裝我們的NDIS協議驅動的時候,系統就會把相關信息寫到註冊表數據庫中,也會在
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\{4d36e972-e325-11ce-bfc1-08002be10318}\XXXX\Linkage
的UpperBind字段中添加我們的NDIS協議名,比如取名 FanxiushuNdisProto,
(名字不能有下劃線,因爲平時習慣了取名帶下劃線。測試了半天才發現的問題)
這個名字必須在inf配置文件中對應名字和
NDIS_PROTOCOL_DRIVER_CHARACTERISTICS 裏邊的Name名字保持一致。
 
 這裏還有一個奇特的地方,我們一般使用inf安裝的驅動,操作系統會根據情況,自動把它運行起來,
而NDIS協議驅動就比較奇特,必須手動把驅動運行起來。等於是一個介於傳統NT方式驅動和PnP驅動的中間產物。
具體啓動方式可以使用命令 “net start 服務名”,或者調用CreateService等API函數。


當驅動運行之後,進入DriverEntry入口函數中,NdisRegisterProtocolDriver 被調用,
系統會從註冊表數據庫中查找綁定到這個協議驅動的所有網卡,
並且對每塊網卡,都調用 ProtocolBindAdapterEx回調函數來指示綁定要求。在這個回調函數中,我們分配自己的資源,
調用 NdisOpenAdapterEx來完成真正的對這塊網卡的綁定,
當NdisOpenAdapterEx返回NDIS_STATUS_PENDING,表示綁定操作不能立即完成,
等待真正完成的時候
ProtocolOpenAdapterCompleteEx 回調函數就會被調用。
當某塊已經調用了NdisOpenAdapterEx實現真正綁定的網卡被卸載或者禁用或者停止等,

ProtocolUnbindAdapterEx 回調函數就會被調用,在此回調函數中我們調用NdisCloseAdapterEx來真正關閉到這塊網卡的綁定。
當NdisCloseAdapterEx返回 NDIS_STATUS_PENDING,表示解綁不能立即完成,
等到真正完成的時候,
ProtocolCloseAdapterCompleteEx回調函數就會被調用。
調用NdisDeregisterProtocolDriver註銷我們的協議驅動的時候,所有真正被綁定的網卡對應的
ProtocolUnbindAdapterEx  都會被調用。
同時當有新網卡被添加的時候,ProtocolBindAdapterEx也會同樣被調用。

以上就是綁定解綁的大致過程,在
NdisRegisterProtocolDriver註冊的時候,系統會遍歷安裝的所有符合這個協議驅動條件的網卡,
並且調用綁定回調函數。之後就根據每塊網卡的重啓,刪除,停止等狀態自動調用綁定和解綁函數。
也可以在驅動裏調用 NdisReEnumerateProtocolBindings 函數,讓系統再次掃描一遍所有安裝的網卡,
對沒有成功綁定的網卡,會再次調用ProtocolBindAdapterEx綁定回調函數。

這裏需要注意的一個問題,也是 NDIS6 和 NDIS5 的一個很大區別,
在NDIS5框架中,使用NdisOpenAdapter函數來打開網卡的綁定,這個函數可以在任何其他地方調用,
不一定非要在 ProtocolBindAdapter回調函數中調用,也一樣能成功綁定。
而NDIS6框架中,對NdisOpenAdapterEx函數做了嚴格限制,它必須在 ProtocolBindAdapterEx回調函數中才能成功調用。
至少NdisOpenAdapterEx第四個參數
BindContext 就必須是 ProtocolBindAdapterEx回調函數傳遞的參數,
我曾嘗試調用NdisOpenAdapterEx時候給BindContext傳遞NULL,結果系統以藍屏來回應。
光就這一個參數就已經杜絕了NdisOpenAdapterEx在其他地方調用的可能。
這給以前開發的協議驅動軟件帶來了不少麻煩,比如winpcap接口庫。
同樣我以前開發的基於NDIS5的橋接虛擬網絡的也是在需要的地方調用NdisOpenAdapter來綁定具體網卡的,
而不是在ProtocolBindAdapter回調函數中,不過我的很好改,無非就是在ProtocolBindAdapterEx中執行真正的綁定,
應用層調用IOCTL查找這個已經綁定的網卡,然後其他地方都跟原來一樣的通訊即可,
因爲一個協議驅動就只服務一個應用層客戶端 。

而winpcap接口庫,(WINPCAP源代碼並沒有仔細去研讀,只是大致看了下驅動部分代碼)
它是在應用層CreateFile一個接口,就會在驅動層調用NdisOpenAdapter一次。
比如應用層創建兩個接口,在驅動就相當於對某塊網卡NdisOpenAdapter調用兩次,等於綁定了兩個協議驅動。
應用層的每個接口都會在驅動層對應的每個協議驅動上截獲數據包,並進行過濾等處理。
而且許多抓包軟件也是根據winpcap的接口的這種設計來工作的。
解決這個問題也不是沒辦法,其實也不難處理。
既然NDIS6對每塊網卡只能調用一次NdisOpenAdapterEx,並且只能在ProtocolBindAdapterEx回調函數中調用。
那就一切按照NDIS6的規則來做。
我們把這個綁定的唯一的真實的實例稱爲 NicProto_Parent, 然後模擬出許多虛擬 NicProto_Child 來跟應用層的每個接口對應,
每個NicProto_Child都掛載到 NicProto_Parent中,當在ProtocolReceiveNetBufferLists 回調函數中接收底層網卡的數據包的時候,
對每個 NicProto_Child都執行接收操作,當然每個NicProto_Child都在接收的時候執行不同的過濾條件。
於是問題就這麼簡單解決了。
WINPCAP一直是NDIS5框架,並且多年前停止更新,有個替代接口叫NPCAP的,不過主要使用的是NDISFilter框架。
只是對此有些疑惑,如果只是作爲純粹的抓包接口,沒必要採用阻斷方式的Ndisfilter中間層驅動,
畢竟在處理數據包的時候,會延緩數據包的傳輸,當然現在的電腦硬件基本看不出效率問題。但是心理上總是感覺怪怪的。

當調用NdisOpenAdapterEx打開某塊網卡的數據包的時候,我們的協議驅動就可以接收這塊網卡發上來的數據包了,
現在我們來關注
ProtocolReceiveNetBufferLists 回調函數,
一般來說,當底層網卡傳輸上來數據的時候,
ProtocolReceiveNetBufferLists回調函數就會被調用,這個很好理解。
 可是再看這些抓包軟件,不單能抓取到從網卡發到系統的數據包,還能抓取到從系統發送出去的數據包。
也就是這個回調函數還能接收到其他地方發來的數據包。

ProtocolReceiveNetBufferLists究竟能接收哪些數據包,是根據對NDIS協議驅動過濾條件設置的,
具體來說就是調用NdisOidRequest來設置 OID_GEN_CURRENT_PACKET_FILTER ,具體介紹請查閱MSDN文檔。
要接收所有數據包(包括從系統發給網卡,或者從網卡發到系統的數據包。)
可以設置
NDIS_PACKET_TYPE_PROMISCUOUS 表示把網卡設置爲混雜模式,這樣所有發給網卡的數據包都能接收到了
(包括從系統發給網卡的,網卡接收到的屬於本網卡的,網卡接收到的不屬於本網卡的,很顯然,這種情況下,網卡非常繁忙)。
也可以設置
NDIS_PACKET_TYPE_ALL_LOCAL 表示所有安裝的NDIS協議驅動發給網卡的
(當然也包括tcpip.sys協議驅動),或者從網卡接收的。
不過更偏向設置 ALL_LOCAL,能減輕網卡負擔,除非你想抓取局域網中的不屬於本網卡的數據包。
ProtocolReceiveNetBufferLists 回調函數不單能接受到從底層傳輸來的數據包,還能接收從其他協議驅動傳輸的數據包,
也就是能接收系統通過這個網卡傳輸的數據包(包括髮送和接收兩個方向)。
這是跟NDIS中間層驅動和底層網卡驅動有區別的地方。也正是這樣的特性,
使得比如windows平臺的vmware網絡裏邊的橋接模式,(vmware的網絡橋接驅動也是一個NDIS協議驅動)
能讓vmware裏邊的虛擬機設置跟宿主機一樣的網段,並且能順利跟宿主機通訊。

當我們的協議驅動,需要發送數據包到網卡,可以調用 NdisSendNetBufferLists函數,然後底層網卡接收到數據包,

調用NdisMSendNetBufferListsComplete完成數據包的發送的時候,協議驅動中對應的 ProtocolSendComplete 回調函數就會被調用。
我們在這個回調函數中釋放在調用NdisSendBufferLists時候分配的NET_BUFFER_LISTS等資源。數據包的發送就完成了。

NdisSendNetBufferLists函數調用的時候,還會判斷是否應該朝綁定到同一塊網卡的其他協議驅動發送這個數據包,
如果是的話,還會調用其他協議驅動的
ProtocolReceiveNetBufferLists 接收回調函數。

最後我們再來看看回環數據包的問題,
什麼是迴環數據包?
也就是發給 127.0.0.1 的數據包,這些數據包不會朝具體的網卡發送,最終都會被傳遞回來。
還有一種是朝本機地址發送的數據包,假設本機地址是192.168.1.100, 朝192.168.1.100發送數據包最終也會被傳遞回來。

比如A進程創建偵聽地址 127.0.0.1,端口1234的socket,B進程朝127.0.0.1 的1234端口發送數據包,
數據包進入操作系統內核,按照一般想法,判斷出地址是朝本機發送的,在傳輸層或者更早之前就應該被彈回來。
但是大部分操作系統還是按照正規流程,進入到網際層也就是IP層,然後再反彈回去。包括windows和linux系統都是這樣。
windows的tcpip.sys驅動在網際層判斷是迴環數據包,自然也就不再朝底層網卡發送,再說127.0.0.1 也沒有對應的網卡。
也就是說 tcpip.sys不再調用NdisSendBufferLists函數發送數據包,而是直接把這個迴環數據包再朝上層傳遞回去。
這個時候,很顯然,NDIS協議驅動是接收不到迴環數據包的。自然也包括NDIS中間層驅動,NDIS網卡驅動也接收不到迴環數據包。

要處理這個迴環數據包,在WINXP這樣的系統,似乎只有TDI可以使用了。
而在WIN7以上的平臺使用WFP來抓取回環數據包。
在WFP的IP層掛鉤

FWPM_LAYER_INBOUND_IPPACKET_V4/FWPM_LAYER_OUTBOUND_IPPACKET_V4, 然後抓取回環數據包。
(WFP的開發,具體可參閱我的WFP文章
Windows7以上使用WFP驅動框架實現IP數據包截取(一)和
Windows7以上使用WFP驅動框架實現IP數據包截取(二)。)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章