2016年8月29日 星期一

TDI Operations - I

Opening a Transport Address

下圖展示了核心模式客戶端何開它底層傳輸驅動的一個傳輸位址:


在開了一個傳輸基於綁定所建立的裝置物件以後,TDI客戶端通常會透過開一個表示為傳輸位址(transport address)的檔案物件來與本地端節點溝通。要做到這個,客戶端EA(extended attributes)緩衝區內設定指定的位址,並將其指定為參數後調用ZwCreateFile。在EA緩衝區內,客戶將EaName設定為系統所定義的TdiTransportAddress,緊接EaName之後的EATDI定義的TRANSPORT_ADDRESS型態。


當客戶端調用ZwCreateFile的時候它可以指定一個傳輸位址是要自己獨享或是與其他客乎端共享。客戶端在成功地開一個獨享的傳輸位址,可以保證在客戶端關閉這個位址前,其他客戶不能使用。

要在網路上做無連線通信(UDP),假設底層傳輸端支援廣播數據報,則客戶端可以在EA緩衝內指定底層的傳輸廣播(broadcast)位址。這樣的位址是公享的不能被任一客戶端所獨佔。

在客戶端調用ZwCreateFile 後,系統的I/O管理員會建立一個客戶端程序特定的檔案物件來表示一個位址,以及調用TDI傳輸驅動的TdiDispatchCreate例程,TDI傳輸驅動會收到一個包含客戶端提供給ZwCreateFile參數的IRPTdiDispatchCreate會分析其中的EA訊息,並且在調用成後以後設定開位址的內部狀態。

在調用ZwCreateFile 成功以後,此函式會回傳客戶端一個檔案handle,而客戶端可以使用ObReferenceObjectByHandle來獲得一個檔案物件的指標,此時,客戶端已經準備好可以建立一個 TDI_XXX IO請求來與底層傳輸做溝通。舉例,客戶端可以在一個開的位址上收發數據報。

一般來說,客戶端首先必須決定是否註冊一個或多個 ClientEventXxx 處理程序以及是否要使用開的位址來與遠端節點進行溝通,在這個例子客戶端必須開一個連接節點(connection endpoint)

如果客戶端想要接收一些網路事件,它可以透過TdiBuildSetEventHandler來安裝請求餅切提交一個或多個TDI_SET_EVENT_HANDLER請求來註冊自己的ClientEventXxx 處例程序。更多有關安裝以及提交IOCTL請求的資訊,Packaging and Submitting IOCTL Requests.

在客戶端註冊完自己的 ClientEventXxx 處理程序以後,它已經準備好在開起的連接節點上溝通,在 Opening a Connection Endpoint中有描述這段過程。而在Sending and Receiving Connectionless Data裡則有描述一個無連線通信。

當一個開的傳輸位址結束網路上的通信,客戶端必須透過調用 ZwClose 來關閉一個表示傳輸位址的檔案物件。詳細的過程在Closing a Transport Address or Control Channel內有描述。

2016年8月15日 星期一

TDI Transports and Their Clients - VI

TDI Requests Versus Events

多數低階網路介面,例如NetBIOS以及Windows Sockets為單向的。無論何時只要客戶端需要,它就可以調用底層的傳輸驅動,但是傳輸驅動卻不能主動呼叫客戶端。只有在傳輸驅動要回覆客戶端一個特定錯誤碼的時候才有機會對他的客戶端"說話"

TDI提供一個事件-通知機制,如果客戶端已經註冊ClientEventXxx 事件處理程序,則允許一個TDI傳輸驅動在一個特定網路事件發生時調用它的核心模式客戶端(例如,接收一個數據報)TDI客戶端支援回呼函式並且處理對應的工作。

Windows2000之後,為了PnP-aware傳輸,TDI也定義了一種方式在一個傳輸建立或是解除自己與底層的NIC綁定的時候,在傳輸驅動在任何特定綁定建立或是中斷一個連線的時候或是在一個系統電源狀態改變的時候,通知它的客戶端這些事件的發生。要接收這些類型的通知,則傳輸驅動的客戶端需要註冊一組ClientPnPXxx的事件處理程序。在TDI Device Objects內有介紹。

當一個TDI傳輸驅動調用客戶端註冊的ClientEventXxx 處理程序,它可以在此呼叫傳送一個作為參數的有限數據給客戶。此特徵允許客戶端不需要配置緩衝區來接收來自傳輸端的訊息。

例如,重定向器藉著此TDI特徵的優點。大多數重定發送給伺服端的請求(例如寫入一個SMBs)小於標準的SMB標頭。此標頭,包括狀態指示器,多重ID等等的長度,是相當小的,通常小於100個字節。當底層傳輸驅動調用重定向註冊的ClientEventReceive處理程序,重定向只需要檢視指示的訊息,注意SMB的回應狀態。在這樣的傳輸交易下,重定向不需要配置緩衝來接收SMB的回應訊息。


TDI Transports and Their Clients - V

TDI Kernel-Mode Client Interactions

下圖展示了TDI客戶端如何製造I/O請求,並且發布此請求給客戶端底層的TDI傳輸驅動。以及傳輸驅動如何呼叫客戶端所註冊的事件回呼函式。


TDI客戶端與它傳輸驅動之間的互動關係如下:
Creating TDI File Objects
客戶端調用ZwCreateFile 來建立或是開一個檔案物件(檔案物件可以是一個傳輸位址, 一個連接終端,或是一個控制通道)。這個調用使得I/O管理員會配置一個IRP,並且會將客戶端支援的參數打包進入IRPI/O管理員還會將這個IRP發布給底層傳輸驅動的TdiDispatchCreate常式。
當傳輸驅動準備好了新檔案物件的所有狀態,它會調用IoCompleteRequest (TdiCompleteRequest)以及回應STATUS_SUCCESS。接者ZwCreateFile 回傳TDI客戶端一個檔案物件的handle。每一個客戶端程序調用ZwCreateFile 來建立一個檔案物件,即便兩個客戶端調用ZwCreateFile指定的是相同的傳輸位址,每個檔案物件還是分開並且獨立的。

Submitting Requests
再建立完檔案物件以後,客戶端可以參考他們的物件來提交請求。例如,在開一個傳輸位址檔案物件後,客戶端可以提交一個詢問位址訊息或是從此位址發送數據報的請求。這樣一個客戶端使用的是標準I/O系統機制來提交所有的請求。
  • 客戶端準備了帶有IRP_MJ_XXX opcodeIRP,這個opcode定義了客戶端想要傳輸驅動履行的操作。客戶端也提供了IRP_MJ_XXX,所需的參數,並且可以自己決定是否要設定IoCompletion常式。WDK包含了一組TdiBuildXxx 巨集(tdikrnl.h),客戶端可以用於準備TDI定義的IOCTL要求,客戶端也可以使用TdiBuildInternalDeviceControl 來配置IRP
  • IRP設定完以後,客戶端調用IoCallDriver,參數為帶有指向IRP位址的指標(IRP內帶有檔案物件(以前提到的三種類型)的位址)以及傳輸驅動的裝置物件。I/O管理員將IRP直接交給傳輸驅動對應的TdiDispatch(Xxx)
  • 當傳輸驅動完成請求操作,它會調用TdiCompleteRequest IoCompleteRequest。如果客戶端對於IRP有註冊IoCompletion,則I/O管理員會調用這個例程。
Handling Event Notifications
如果客戶端已經註冊一個或多個事件,則當相關的網路事件發生時,傳輸驅動會調用客戶端的事件處理程序(handler)。例如,如果客戶端註冊一個ClientEventReceive處理程序,則當本地端接收來自於遠端節點的數據時,傳輸驅動就會調用事件處理程序。

Deleting TDI Objects
客戶端調用ZwClose 來移除一個檔案物件,當客戶端不在需要他們的時候。客戶端的關閉請求會轉發至傳輸驅動的TdiDispatchCleanup,以及隨後再轉發到TdiDispatchClose


2016年8月12日 星期五

TDI Transports and Their Clients - IV

TDI Transport Driver Routines

每一個符合TDI標準的傳輸驅動程式必須導出(export)一組進入點(entry points)來給I/O管理員調用。

一些標準TDI傳輸驅動的例程(routines),如初始或是卸載(unload)例程由驅動自己本身負責調用。其他標準的派遣(Dispatch)例程,當TDI客戶調用系統所支援的例程時,由I/O管理員負責調用,例如ZwCreateFile 以及 IoCallDriver

就像其他的核心模式驅動一樣,一個TDI傳輸的DriverEntry例程設定一個或者多個派遣(Dispatch)例程來處理多種類型的I/O請求(IRPs)TDI驅動可以導出單一個派遣歷程來處理所有進來的IRPs或是一個個分開的DispatchXxx來處理每一個驅動所支援的IRP_MJ_XXX有關更多特定TDI要求的派遣例程,請參考TDI Routines, Macros, and Callbacks

當一個TDI傳輸完成一個客戶端的要求,I/O管理員會調用IoCompletion,此IoCompletion會在客戶端提交I/O請求給底層傳輸之前設定在IRP內部。

另外,當特定的網路事件發生時,傳輸驅動必須調用先前在TDI客戶所註冊的事件處理(event handler)。這些客戶端支援的事件處理會總結在TDI Routines, Macros, and Callbacks以及也會在Network Driver Reference章節討論。

在傳輸層下緣,TDI驅動必須導出一組ProtocolXxx 函式,此組函式會底層NDIS中間層或是NIC驅動透過NDIS函式庫調用。這些NDIS驅動下緣函式在本篇更早以前就有介紹(我想他指的是NDIS的章節)


TDI Transports and Their Clients - III

TDI File Objects

所有的I/O請求被定向到一個打開的檔案物件,檔案物件可以表示為一個實體或是虛擬裝置,一個數據檔案,或是任何I/O請求的邏輯目標。檔案物件可以透過特定的程序開。舉例來說,I/O管理員對於每一個程序打開的特定數據檔案創建一個檔案物件。

TDI使用檔案物件來表示存在於網路環境下的任何實體。如下為TDI定義的實體類型:

TDI傳輸驅動以及底層的NDIS驅動提供一個機制,讓數據可以在一個程序上的網路節點轉移到另一個或多個程序上的網路節點上(遠端程序彼此的網路節點可能相同或不同)。前面提到的位址以及連接端點檔案物件可以展示了這樣的轉移發生手段

舉例來說,Windows2000及之後的版本的重定向程序開了兩個檔案物件,一個是傳輸位址而另外一個是關聯到此傳輸位址的連接端點,發送SMB(Server Message Block)訊息到一個遠端節點上的伺服程序以及接收來自遠端節點伺服程序的數據。


TDI支援的數據傳輸方式有不可靠的非連線與可靠連線導向這兩種。

不可靠的非連線數據可以發送至遠端節點程序(一個或是群組程序)中已經開的特定傳輸位址。當發送不可靠非連線數據,如數據報(UDP,datagrams),發送者只需要辨認遠端節點的位址。

如果一個端點-端點的連線(或稱作一個虛擬線路)已經建立,則可靠的連線導向數據傳輸可以在這兩個端點程序之間傳輸。端點-端點的連線是兩者之間的一對一關聯,並且只有兩個程序。要建立這樣的連線,一個程序必須要辨認另一個他想建立連線的程序。每個這樣的程序(client - server)必須開一個傳輸位址以及開各自網路節點上的連接端點,並且各自關聯打開的傳輸位址與連接端點。更多有關連接端點請看File Objects Represent Connection Endpoints.

TDI實體用於辨認一個特定的程序或者一個群的程序,TDI實體為一個或多個開特定程序的檔案物件,此檔案物件表示一個特定的傳輸位址(transport address)。這樣一個檔案物件包含傳輸指標(transport-supplied pointers)用於驅動維護特定程序的識別狀態以及程序所在的節點。一個”routable”傳輸狀態,例如TCP/IP, Mcsxns, AppleTalk 以及NWLink,維護有關這樣的位址也包含了識別節點所在網路(subnet)的資訊。

某些TDI定義的傳輸位址種類容納明示與暗示,指示位址辨識單一程序(為一位址),或是可以辨識一個群的程序(群位址)。在群位址的例子,TDI定義的位址可以包含辨識特定程序的資訊。

TDI支援多種的位址類型,下面介紹格式以及三種常用的TDI位址類型(這裡只會翻譯TDI_ADDRESS_IP,而NETBIOSIPX請參考原文):

TDI_ADDRESS_IP
IP類型位址包含一個port號碼以及一個標準Internet Protocol (IP)位址。因為TCP/IP允許在多個節點上的程序註冊相同的port號碼。IP位址需要唯一的識別該節點,以及port號碼需要唯一的識別該節點上的程序。在無連線的數據傳輸(使用TCP/IP中的UDP協議),可以在多個程序註冊相同的port號碼。另外,某些IP位址可以用於多個節點。數據發送至TDI位址(包含IP位址與port號碼),並且會接收來自於IP位址上所有節點的數據:數據會通過所有註冊特定(UDP)port號碼的程序。

File Objects Represent Connection Endpoints

檔案物件表示為一個開的連接端點(connection endpoint),此檔案物件可以識別為一個本地程序與遠端程序跨過網路彼此溝通的特定連結。

任何這樣的檔案物件必須與另一個開的檔案物件(特定的傳輸位址)彼此關聯。傳輸位址可以識別為一個程序,在 File Objects Represent Transport Addresses已經介紹過了。

在類似的方法中,傳輸(transport)使用本地連結端點的檔案物件來維護有關遠端節點的程序狀態,例如遠端節點傳輸位址與它的本地節點客戶端建立的端點到端點的連接。

客戶端程序可以在他自己與其他程序或是遠端節點之間建立多個端點-端點連接。例如,重定向在他自己與每一個遠端伺服端之間建立分開的連結。每一個客戶端可以擁有多個開的連接端點,並且分別地與同個傳輸位址互相的關聯。

每一個連接終端檔案物件用於區分在同一個程序上所建立的端點-端點連接。

2016年8月9日 星期二

TDI Transports and Their Clients - II

TDI Device Objects

Windows 2000以及之後的版本,傳輸(Transports)支援了隨插即用以及電源管理,透過建立的裝置物件表示各自(協議驅動)與底層網卡(NICs)之間的綁定。每一個傳輸(Transport)註冊自己TDI,在系統動期間,當底層網路硬體上線工作時,作為網路提供者,提供給客戶端使用。客戶端註冊各自的ClientPnPXxx回呼函式,接收來自於傳輸端綁定的NIC的通知。以及在有效的傳輸網路位址收發網路數據。

一個傳輸(transport)透過ProtocolBindAdapter建立一個底層NIC綁定。這是由傳輸堆疊中最低的模組(或是堆疊中只有單一的傳輸驅動)透過NDIS註冊。TDI傳輸驅動程式建立一個命名裝置物件(named device object)來表示與底層NDIS微阜驅動之間的綁定。在系統動期間,PnP-aware傳輸透過TDI註冊命名裝置物件並且設定它的綁定狀態。傳輸也會透過TDI在每一個綁定註冊任何已知的網路位址並且設定每一個註冊的位址狀態。如果在最少一個綁定已經準備好透過網路傳輸資料,則NDIS在傳輸堆疊底部調用ProtocolPnPEvent函式以及PnP的事件代碼(NET_PNP_EVENT)為 NetEventBindsComplete

TDI給定每個已經註冊過ClientPnPXxx handlers的網路客戶(TDI client)ClientPnPBindingChange例程中一個機會,客戶端可以透過ZwCreateFile來綁定自己與裝置物件(由傳輸所建立的)。稍後TDI對於底層傳輸已經註冊的網路位址會多次調用客戶端所註冊的ClientPnPAddNetAddress當任何傳輸指示它準備去接收網路傳輸以及當所有綁定已經建立,則TDI也會透過ClientPnPBindingChange通知它的客戶端。

在運行期間,每當一個動態的綁定變化發生,這些PnP-aware傳輸(NDIS)仍會持續的調用TDI,無論因為一個NIC已經/禁用或是因為使用者動一個綁定變化,每當這樣一個傳輸如下製造或是摧毀一個遠端節點的連線:
  • 如果一個新的NIC用,則NDIS會在每一個傳輸堆疊底部調用ProtocolBindAdapter,從而賦予傳輸(transport)一個機會去綁定自己與一個新的NIC,傳輸會創建另一個裝置物件來表示一個新的綁定,並且向TDI註冊新的裝置物件。當註冊了這樣一個傳輸所建立的裝置物件,TDI會調用註冊的ClientPnPBindingChange例程並且提供這些客戶(TDI clients)一個機會來打開一個新的綁定。
  • 一個現存的NIC被禁用之前,通常NDIS會調用ProtocolPnPEvent 函式來詢問是否可以安全的移除NIC。接者,傳輸會通知TDI一個請求來移除NIC,並且TDI會透過客戶註冊的ClientPnPPowerChange例程來通知客戶。如果沒有客戶對象要被拆除(removal),在稍後NDIS會調用ProtocolUnbindAdapter來解除綁定。在這個過程中,傳輸調用TDI來反註冊之前綁定過的所有的網路位址。接者,TDI透過ClientPnPDelAddressClientPnPBindingChange來通知客戶解除綁定的工作。
  • 如果終端使用者動一個綁定變換,NDIS會直接呼叫TDI來通知網路客戶綁定變化。如果沒有客戶物件,NDIS會調用ProtocolPnPEventProtocolBindAdapter, 或是ProtocolUnbindAdapter

以類似的方式,TDI通知客戶有關底層NIC電源狀態的改變。當系統電源管理調用NDIS來開關NIC的電源,NDIS會調用ProtocolPnPEvent函式,接者每一個傳輸會通知TDI電源改變,並且TDI再轉發通知給傳輸端的客戶事件發生

2016年8月2日 星期二

TDI Transports and Their Clients - I

TDI Transports and Their Clients

本章描述了TDI傳輸驅動(TDI transport drivers)以及TDI 核心模式客戶(TDI kernel mode clients)之間的關係。例如一個高階層的網路介面模擬器,重定向器(redirectors)或是一個服務(servers)使用系統所定義的TDI介面來與傳輸協議層打交道。這裡探討相關的資訊包含如下:
  • PnP-aware TDI transports如何使用裝置物件(device object)來表示它的傳輸層與底層NIC間的綁定。
  • PnP-aware TDI transports如何動態註冊可用的網路地址,讓TDI客戶端通過使用這些綁定。
  • TDI如何使用檔案物件(file objects)來表示網路實體。
  • 傳輸驅動TDI要求的常式(routines)
  • 客戶(TDI Client)與傳輸(TDI Transport)間的互動。
  • 區分TDI請求與TDI事件。


Transport Driver Interface

下圖展示TDI客戶與傳輸驅動之間的關係。

如該圖所示,在所有傳輸協議堆疊的上層邊界上定義了一個核心模式的網路接口,稱作傳輸驅動接口(TDI)。對於在TDI接口之上的核心模式網路客戶,每一個堆疊中最高層級的協議驅動都支援客戶端TDI接口。該接口包含如下:
  • 每一個TDI傳輸驅動會導出(export)一組標準的核心模式中間層驅動派遣例程(Dispatch routines)。上層的客戶端可以提交I/O請求(IRPs)來調用支援的例程,例如Zw..File routines或是 IoCallDriver
  • 每一個TDI客戶端可以向下層的傳輸驅動註冊自己的ClientEventXxx 回呼例程,每當指定的網路事件發生,下層可以調用回呼例程並且通知客戶端接收事件發生。
  • 每一個TDI客戶端可以向下層的傳輸驅動註冊自己的ClientPnPXxx 回呼例程,下層可以調用此回呼例程並且通知客戶端接收一個來自於PnP的變動發生,例如動態綁定(binding),網路位址,以及電源狀態等變動。
  • 傳輸與客戶端可以調用系統所支援的TdiXxx 函式來與對方溝通。
  • 客戶端如果要向下層的傳輸協議提交一個I/O請求,可以使用一組系統所支援的TdiBuildXxx 巨集以及函式來建立此I/O請求。


要求所有的傳輸驅動程式公開一個單一通用介面(TDI)簡化了傳輸驅動程式的開發工作,這讓所有的傳輸驅動只需要支援單一定義的接口。這也簡化了客戶端的開發工作,TDI最大限度地減少客戶端在指定傳輸所必須撰寫的編碼量。

Windows2000以及之後作業系統版本包含了多個常用的網路接口模組,例如Windows Socket以及NetBIOS。每一個接口都公開一組原始的函式,讓來自於使用者模式的程式可以用於調用訪問。當被調用時,接口模組會對原始函式,關聯的參數,以及程序規則映射一個或多個底層TDI傳輸驅動的調用。

下列包含了TDI的主要特徵:

High Level of Granularity 
TDI容納所有流行的網路接口,因為它在本質上是相對粒度,具有可以混合和匹配的特性,以適應從現有的網絡接口功能映射至幾個小的TDI定義請求。

Asynchronous Operation
大多數核心模式的TDI操作為異步的,使用客戶端提供的回呼例程來指示異步網路事件的發生,以及通知客戶端提交的異步IRPs已經完成。

32-bit Addressing and Values
如同Windows2000以及之後版本的核心模組一樣,TDI傳輸以及它的客戶端為32-bit碼。TDI定義的結構以及參數使用32-bit的指標以及

Flexible Addressing Scheme
TDI並不強制要求任何特定的位址格式,如舊式作業系統定義的16字符的NetBIOS名稱。相反,它採用了由許多位址格式可以識別和使用的可擴展的機制。

Extensible Communication
TDI定義了TDI_ACTION IOCTL要求,任何TDI傳輸可以用於支援一個傳輸操作集合。這允許客戶端提交一個TDI裡沒有具體定義的傳輸特殊要求給下層傳輸驅動。

Event Notification
TDI定義一個計劃,傳輸(transports)可以通知客戶端它有興趣的網路事件已經發生,而不需要客戶端明確的提交一個I/O請求。

Plug and Play Event Notifications
TDI定義一個計畫,在Windows2000以後傳輸端可以通知它的客戶端某些PnP事件,例如底層NIC的可用性和本地網路位址連接的創建/刪除。

System Power-State Change Notifications
TDI定義一個計畫,傳輸在Windows2000之後可以通知它的客戶系統電源狀態的改變,從而給客戶端一個機會保持通電的活動連接。


下列的特徵可以由TDI傳輸驅動程式決定額外的支援:

Data Transfer Modes
TDI支援發送與接收離散數據訊息((message mode)或是一個位元流(byte-stream mode)。支援其一或是全部支援取決於傳輸驅動的寫法或是協議的性質。

Internal Buffering 
TDI傳輸可以在內部作客戶端的收發緩衝。傳輸內部緩衝允許TDI客戶設定以及詢問驅動內部緩衝的大小,要求一個非同步發送操作,接收可用緩衝區空間的通知,以及在客戶端接收數據之前先監看緩衝區內的數據。


Management Options 
所有的傳輸(transport) 對於他們自己的特徵,限制,以及運行的統計數據維護一些TDI定義的狀態。這允許每一個客戶端動態的詢問或是在一些狀況下去設定傳輸提供者靜態資訊,統計數據以及參數設定。前面有提到的TDI_ACTION IOCTL就是這樣的例子。允許TDI傳輸驅動實作獨一無二的網管特徵,然後讓它的客戶端藉著action要求來使用它。

Quality of Service (QoS) 
TDI傳輸可以在建立網路連結上提供QoS協商。另外,一個驅動可以在無連接的數據報傳輸上支援QoS。要支援其一,TDI定義的建立連結以及數據報發送請求包含參數Options 以及OptionsLength,這允許一個TDI客戶端包含特定傳輸,可變長度計算字符串,指定的QoS選項。事實上,Options OptionsLength參數可用於在驅動程序的作者,而不僅僅是QoS規範。

Expedited Delivery 
當發送訊息,客戶端可以標記特定的訊息為急件(expedited)。在發送端,下層傳輸這些訊息在非急件(nonexpedited)訊息之前。而在接收端,急件訊息被指示到客戶端在非急件消息之前。

Chained Receive Indications 
如果下層的NDIS微阜驅動程式(NDIS miniport)支援多封包接收指示,則一個TDI傳輸可以賦予它的客戶端在單一調用中直接讀取一個完整的TSDU,並且客戶可以保留包含TSDU緩衝區的控制直到它消耗完只示的數據。這項特徵透過減少調用接收指示來增進傳輸與客戶端的效能