驱动开发之五 --- TDI之二 【译文】
转自 http://hi.baidu.com/combojiang/blog/item/ae7a4913515753856538dbff.html
接上:
传输设备接口
前面socket知识的了解是为了让你对TDI API做好准备。传输设备接口是一组用于驱动中, 与传输协议驱动通讯的API. 就像TCP。传输驱动实现了这组API,所以你的驱动能够与它通讯。
这比socket的使用多少有些复杂。MSDN上的文档资料会让你更加迷惑,而不是有帮助。所以我们将一步一步地建立一个客户端连接。一旦你理解了这个,你就能够使用这些API来执行其他的操作,例如创建一个服务端等等。
体系结构
下图描述了TDI / NDIS的关系。通常,TDI是标准的传输协议接口,协议驱动开发者可以在他们的驱动中实现这些。在这种情况下,开发者希望使用他们自己的协议来实现标准的接口,而不是希望每个他们支持的协议各自实现一套接口产生争执。这并不是说这些开发者只能被限定实现TDI..他们可以在他们驱动的顶层实现任何自己想要的接口。我不是一个NDIS方面的专家,所以我简单的说这么多,希望不会出现什么错误!总之,这些典型的信息了解了,是有好处的,但由于我们只是开发TDI客户端驱动,所以这些我们并不需要去理解。
NDIS协议驱动
在这个驱动的下层,协议驱动会调用NDIS接口API,协议驱动的作用也就是实现一个协议,并且与NDIS会话,驱动的上层可以是一个私有的接口或者TDI或者两者都有. 顺便说一下,这里没有“NDIS客户端”。他们不存在。如果其他站点有指出这些驱动是“NDIS客户端”的,那是完全错误的。我曾经跟一个NDIS专家谈到“NDIS客户端”,他们不知道我所说的是什么。
NDIS中间层驱动
下层是中间层驱动,这些驱动可以用作传输,包调度或者数据过滤。
NDIS小端口驱动
最后一层是NDIS小端口驱动,他们直接与NID物理设备通讯。
你可以在msdn上找到更多的关于TDI和NDIS的体系结构信息。
步骤1:打开传输地址
第一步是创建一个“传输地址”的句柄,你需要使用ZwCreateFile 创建一个“传输地址“实例句柄。这个“传输地址“是指本机的ip地址。这不是远程计算机的ip地址。背后的原因是例如,在本机安装了多个NIC, 在本机有多个IP地址的情况下,让你绑定一个指定的IP地址。
你也可以简单的指定为 " 0.0.0.0 " 使用任意一块NIC.
打开句柄的办法对于那些不熟悉驱动开发的人来讲有点生硬,你必须指定“EA " 或者 " Extedned Attributes " ,通过IRP_MJ_CREATE把它传递给驱动。你还可以在打开时,传递参数。除了可以在dos设备名的后面添加外,这时,你也能指定本地端口。如果你是在创建一个服务端,就需要在这个时候指定端口。既然我们仅仅是实现一个客户端连接,因此,我们必须无需关心端口,只要设置为0就行。
下面代码举例说明了怎样打开一个传输地址。 /**/ /*
typedef struct _FILE_FULL_EA_INFORMATION {
ULONG NextEntryOffset;
UCHAR Flags;
UCHAR EaNameLength;
USHORT EaValueLength;
CHAR EaName[1];
} FILE_FULL_EA_INFORMATION, *PFILE_FULL_EA_INFORMATION;
*/
NTSTATUS TdiFuncs_OpenTransportAddress(PHANDLE pTdiHandle,
PFILE_OBJECT * pFileObject)
{
NTSTATUS NtStatus = STATUS_INSUFFICIENT_RESOURCES;
UNICODE_STRING usTdiDriverNameString;
OBJECT_ATTRIBUTES oaTdiDriverNameAttributes;
IO_STATUS_BLOCK IoStatusBlock;
char DataBlob[sizeof(FILE_FULL_EA_INFORMATION) +
TDI_TRANSPORT_ADDRESS_LENGTH + 300] = {0};
PFILE_FULL_EA_INFORMATION pExtendedAttributesInformation =
(PFILE_FULL_EA_INFORMATION)&DataBlob;
UINT dwEASize = 0;
PTRANSPORT_ADDRESS pTransportAddress = NULL;
PTDI_ADDRESS_IP pTdiAddressIp = NULL;
/**//*
* Initialize the name of the device to be opened. ZwCreateFile takes an
* OBJECT_ATTRIBUTES structure as the name of the device to open.
* This is then a two step process.
*
* 1 - Create a UNICODE_STRING data structure from a unicode string.
* 2 - Create a OBJECT_ATTRIBUTES data structure from a UNICODE_STRING.
*
*/
RtlInitUnicodeString(&usTdiDriverNameString, L"\\Device\\Tcp");
InitializeObjectAttributes(&oaTdiDriverNameAttributes,
&usTdiDriverNameString,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
NULL, NULL);
/**//*
* The second step is to initialize the Extended Attributes data structure.
*
* EaName = TdiTransportAddress, 0, TRANSPORT_ADDRESS
* EaNameLength = Length of TdiTransportAddress
* EaValueLength = Length of TRANSPORT_ADDRESS
*/
RtlCopyMemory(&pExtendedAttributesInformation->EaName,
TdiTransportAddress,
TDI_TRANSPORT_ADDRESS_LENGTH);
pExtendedAttributesInformation->EaNameLength =
TDI_TRANSPORT_ADDRESS_LENGTH;
pExtendedAttributesInformation->EaValueLength =
TDI_TRANSPORT_ADDRESS_LENGTH +
sizeof(TRANSPORT_ADDRESS) +
sizeof(TDI_ADDRESS_IP);
pTransportAddress =
(PTRANSPORT_ADDRESS)(&pExtendedAttributesInformation->EaName +
TDI_TRANSPORT_ADDRESS_LENGTH + 1);
/**//*
* The number of transport addresses
*/
pTransportAddress->TAAddressCount = 1;
/**//*
* This next piece will essentially describe what
* the transport being opened is.
* AddressType = Type of transport
* AddressLength = Length of the address
* Address = A data structure that is essentially
* related to the chosen AddressType.
*/
pTransportAddress->Address[0].AddressType = TDI_ADDRESS_TYPE_IP;
pTransportAddress->Address[0].AddressLength = sizeof(TDI_ADDRESS_IP);
pTdiAddressIp =
(TDI_ADDRESS_IP *)&pTransportAddress->Address[0].Address;
/**//*
* The TDI_ADDRESS_IP data structure is essentially simmilar to
* the usermode sockets data structure.
* sin_port
* sin_zero
* in_addr
*
*NOTE: This is the _LOCAL ADDRESS OF THE CURRENT MACHINE_ Just as with
* sockets, if you don't care what port you bind this connection to t
* hen just use "0". If you also only have one network card interface,
* there's no reason to set the IP. "0.0.0.0" will simply use the
* current machine's IP. If you have multiple NIC's or a reason to
* specify the local IP address then you must set TDI_ADDRESS_IP
* to that IP. If you are creating a server side component you may
* want to specify the port, however usually to connectto another
* server you really don't care what port the client is opening.
*/
RtlZeroMemory(pTdiAddressIp, sizeof(TDI_ADDRESS_IP));
dwEASize = sizeof(DataBlob);
NtStatus = ZwCreateFile(pTdiHandle, FILE_READ_EA | FILE_WRITE_EA,
&oaTdiDriverNameAttributes,
&IoStatusBlock, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN_IF, 0,
pExtendedAttributesInformation, dwEASize);
if(NT_SUCCESS(NtStatus))
{
NtStatus = ObReferenceObjectByHandle(*pTdiHandle,
GENERIC_READ | GENERIC_WRITE,
NULL,
KernelMode,
(PVOID *)pFileObject, NULL);
if(!NT_SUCCESS(NtStatus))
{
ZwClose(*pTdiHandle);
}
}
return NtStatus;
}
传输设备接口
前面socket知识的了解是为了让你对TDI API做好准备。传输设备接口是一组用于驱动中, 与传输协议驱动通讯的API. 就像TCP。传输驱动实现了这组API,所以你的驱动能够与它通讯。
这比socket的使用多少有些复杂。MSDN上的文档资料会让你更加迷惑,而不是有帮助。所以我们将一步一步地建立一个客户端连接。一旦你理解了这个,你就能够使用这些API来执行其他的操作,例如创建一个服务端等等。
体系结构
下图描述了TDI / NDIS的关系。通常,TDI是标准的传输协议接口,协议驱动开发者可以在他们的驱动中实现这些。在这种情况下,开发者希望使用他们自己的协议来实现标准的接口,而不是希望每个他们支持的协议各自实现一套接口产生争执。这并不是说这些开发者只能被限定实现TDI..他们可以在他们驱动的顶层实现任何自己想要的接口。我不是一个NDIS方面的专家,所以我简单的说这么多,希望不会出现什么错误!总之,这些典型的信息了解了,是有好处的,但由于我们只是开发TDI客户端驱动,所以这些我们并不需要去理解。
NDIS协议驱动
在这个驱动的下层,协议驱动会调用NDIS接口API,协议驱动的作用也就是实现一个协议,并且与NDIS会话,驱动的上层可以是一个私有的接口或者TDI或者两者都有. 顺便说一下,这里没有“NDIS客户端”。他们不存在。如果其他站点有指出这些驱动是“NDIS客户端”的,那是完全错误的。我曾经跟一个NDIS专家谈到“NDIS客户端”,他们不知道我所说的是什么。
NDIS中间层驱动
下层是中间层驱动,这些驱动可以用作传输,包调度或者数据过滤。
NDIS小端口驱动
最后一层是NDIS小端口驱动,他们直接与NID物理设备通讯。
你可以在msdn上找到更多的关于TDI和NDIS的体系结构信息。
步骤1:打开传输地址
第一步是创建一个“传输地址”的句柄,你需要使用ZwCreateFile 创建一个“传输地址“实例句柄。这个“传输地址“是指本机的ip地址。这不是远程计算机的ip地址。背后的原因是例如,在本机安装了多个NIC, 在本机有多个IP地址的情况下,让你绑定一个指定的IP地址。
你也可以简单的指定为 " 0.0.0.0 " 使用任意一块NIC.
打开句柄的办法对于那些不熟悉驱动开发的人来讲有点生硬,你必须指定“EA " 或者 " Extedned Attributes " ,通过IRP_MJ_CREATE把它传递给驱动。你还可以在打开时,传递参数。除了可以在dos设备名的后面添加外,这时,你也能指定本地端口。如果你是在创建一个服务端,就需要在这个时候指定端口。既然我们仅仅是实现一个客户端连接,因此,我们必须无需关心端口,只要设置为0就行。
下面代码举例说明了怎样打开一个传输地址。 /**/ /*
typedef struct _FILE_FULL_EA_INFORMATION {
ULONG NextEntryOffset;
UCHAR Flags;
UCHAR EaNameLength;
USHORT EaValueLength;
CHAR EaName[1];
} FILE_FULL_EA_INFORMATION, *PFILE_FULL_EA_INFORMATION;
*/
NTSTATUS TdiFuncs_OpenTransportAddress(PHANDLE pTdiHandle,
PFILE_OBJECT * pFileObject)
{
NTSTATUS NtStatus = STATUS_INSUFFICIENT_RESOURCES;
UNICODE_STRING usTdiDriverNameString;
OBJECT_ATTRIBUTES oaTdiDriverNameAttributes;
IO_STATUS_BLOCK IoStatusBlock;
char DataBlob[sizeof(FILE_FULL_EA_INFORMATION) +
TDI_TRANSPORT_ADDRESS_LENGTH + 300] = {0};
PFILE_FULL_EA_INFORMATION pExtendedAttributesInformation =
(PFILE_FULL_EA_INFORMATION)&DataBlob;
UINT dwEASize = 0;
PTRANSPORT_ADDRESS pTransportAddress = NULL;
PTDI_ADDRESS_IP pTdiAddressIp = NULL;
/**//*
* Initialize the name of the device to be opened. ZwCreateFile takes an
* OBJECT_ATTRIBUTES structure as the name of the device to open.
* This is then a two step process.
*
* 1 - Create a UNICODE_STRING data structure from a unicode string.
* 2 - Create a OBJECT_ATTRIBUTES data structure from a UNICODE_STRING.
*
*/
RtlInitUnicodeString(&usTdiDriverNameString, L"\\Device\\Tcp");
InitializeObjectAttributes(&oaTdiDriverNameAttributes,
&usTdiDriverNameString,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
NULL, NULL);
/**//*
* The second step is to initialize the Extended Attributes data structure.
*
* EaName = TdiTransportAddress, 0, TRANSPORT_ADDRESS
* EaNameLength = Length of TdiTransportAddress
* EaValueLength = Length of TRANSPORT_ADDRESS
*/
RtlCopyMemory(&pExtendedAttributesInformation->EaName,
TdiTransportAddress,
TDI_TRANSPORT_ADDRESS_LENGTH);
pExtendedAttributesInformation->EaNameLength =
TDI_TRANSPORT_ADDRESS_LENGTH;
pExtendedAttributesInformation->EaValueLength =
TDI_TRANSPORT_ADDRESS_LENGTH +
sizeof(TRANSPORT_ADDRESS) +
sizeof(TDI_ADDRESS_IP);
pTransportAddress =
(PTRANSPORT_ADDRESS)(&pExtendedAttributesInformation->EaName +
TDI_TRANSPORT_ADDRESS_LENGTH + 1);
/**//*
* The number of transport addresses
*/
pTransportAddress->TAAddressCount = 1;
/**//*
* This next piece will essentially describe what
* the transport being opened is.
* AddressType = Type of transport
* AddressLength = Length of the address
* Address = A data structure that is essentially
* related to the chosen AddressType.
*/
pTransportAddress->Address[0].AddressType = TDI_ADDRESS_TYPE_IP;
pTransportAddress->Address[0].AddressLength = sizeof(TDI_ADDRESS_IP);
pTdiAddressIp =
(TDI_ADDRESS_IP *)&pTransportAddress->Address[0].Address;
/**//*
* The TDI_ADDRESS_IP data structure is essentially simmilar to
* the usermode sockets data structure.
* sin_port
* sin_zero
* in_addr
*
*NOTE: This is the _LOCAL ADDRESS OF THE CURRENT MACHINE_ Just as with
* sockets, if you don't care what port you bind this connection to t
* hen just use "0". If you also only have one network card interface,
* there's no reason to set the IP. "0.0.0.0" will simply use the
* current machine's IP. If you have multiple NIC's or a reason to
* specify the local IP address then you must set TDI_ADDRESS_IP
* to that IP. If you are creating a server side component you may
* want to specify the port, however usually to connectto another
* server you really don't care what port the client is opening.
*/
RtlZeroMemory(pTdiAddressIp, sizeof(TDI_ADDRESS_IP));
dwEASize = sizeof(DataBlob);
NtStatus = ZwCreateFile(pTdiHandle, FILE_READ_EA | FILE_WRITE_EA,
&oaTdiDriverNameAttributes,
&IoStatusBlock, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN_IF, 0,
pExtendedAttributesInformation, dwEASize);
if(NT_SUCCESS(NtStatus))
{
NtStatus = ObReferenceObjectByHandle(*pTdiHandle,
GENERIC_READ | GENERIC_WRITE,
NULL,
KernelMode,
(PVOID *)pFileObject, NULL);
if(!NT_SUCCESS(NtStatus))
{
ZwClose(*pTdiHandle);
}
}
return NtStatus;
}